better-ui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

UI polish

UI 打磨

This skill holds the visual polish for surfaces, icons and motion, with the exact value each detail takes. It applies once the underlying interaction is sound, and a polish finding never outranks a broken interaction.
此 skill 规定了界面层、图标和动效的视觉打磨方式,并明确每个细节应采用的精确数值。只有在底层交互可靠后才应用这些规范;视觉打磨问题绝不应凌驾于交互故障之上。

Exact values, optional polish

精确数值,酌情打磨

The values below are exact, not ranges to approximate.
cubic-bezier(0.2, 0, 0, 1)
is not
cubic-bezier(0.4, 0, 0.2, 1)
, and
0.96
is not
0.95
. The optical nudges and the concentric padding cutoff are the exceptions. They are starting points, judged by eye.
Keep the project's component library, tokens and density, and match its motion language wherever no rule here gives a value. A deliberate and consistent project convention, such as a style with no shadows, is a preference and not a finding. The same detail done two ways within the project is a finding.
Text wrapping, font rendering, tabular numbers and text spacing belong to
better-typography
. Hit areas, keyboard support, ARIA and the reduced-motion requirement belong to
better-accessibility
. Grouping, section spacing, breakpoints and spatial RTL belong to
better-layout
, except directional icon mirroring. Color tokens and contrast measurement belong to
better-colors
.
以下数值均为精确值,不是可供近似取值的范围。
cubic-bezier(0.2, 0, 0, 1)
不是
cubic-bezier(0.4, 0, 0.2, 1)
,而
0.96
也不是
0.95
。光学微调与同心内边距阈值是例外;它们仅是起点,需通过肉眼判断。
保留项目现有的组件库、design token 和密度;本 skill 未规定具体数值的地方,应与项目现有的动效语言保持一致。项目中有意且一致的约定(例如不使用阴影的风格)属于偏好,不应作为问题。同一细节在项目内采用两种实现方式,则应作为问题报告。
文本换行、字体渲染、tabular numbers 和文本间距属于
better-typography
。点击区域、键盘支持、ARIA 和 reduced-motion 要求属于
better-accessibility
。分组、章节间距、断点和空间 RTL 属于
better-layout
,但方向性图标镜像除外。颜色 token 和对比度测量属于
better-colors
。

Outer radius equals inner radius plus padding

外层圆角等于内层圆角加 padding

Where nested surfaces share a visible, even inset, the outer radius is the inner radius plus the padding plus any border width. Past
24px
of padding, or where the padding is deliberately asymmetric, treat the layers as separate surfaces and keep each one's radius token. Recipes are in surfaces.md.
当嵌套界面共享可见且均匀的 inset 时,外层圆角等于内层圆角加 padding,再加任何 border 宽度。当 padding 超过
24px
,或 padding 有意采用非对称布局时,应将各层视为独立界面,并分别保留各自的 radius token。相关 recipe 见 surfaces.md。

Align optically where geometry looks off

在几何布局显得不协调时进行光学对齐

Where geometric centering looks off, nudge by eye. Give a button
2px
less padding on its icon side, shift a play triangle toward its point and fix asymmetric glyphs in the SVG itself. Recipes are in surfaces.md.
如果几何居中看起来不协调,请凭视觉微调。按钮图标一侧的 padding 应减少
2px
,播放三角形应朝其尖端方向移动,不对称字形则应直接在 SVG 本身中修正。相关 recipe 见 surfaces.md。

Shadows for elevation, borders for structure

用阴影表现高度,用边框表达结构

Where a border exists only to create depth, replace it with layered transparent
box-shadow
values. Keep borders on dividers, separators, table cells and selected states. Keep them on form inputs too, whose boundary needs 3:1 non-text contrast under
better-accessibility
. Focus rings belong to
better-accessibility
as well.
Forced-colors mode removes every
box-shadow
. Keep
border: 1px solid transparent
under a shadow ring so that mode still draws an edge. Recipes are in surfaces.md.
如果 border 仅用于营造深度感,应将其替换为分层透明
box-shadow
值。border 应保留在分割线、分隔符、表格单元格和选中状态上。表单输入框也应保留 border;根据
better-accessibility
,其边界需要 3:1 的非文本对比度。focus ring 同样属于
better-accessibility
的范畴。
forced-colors 模式会移除所有
box-shadow
。应在 shadow ring 下保留
border: 1px solid transparent
,使该模式仍能绘制边缘。相关 recipe 见 surfaces.md。

Outline images in pure black or white

为图像描边时仅使用纯黑或纯白

Give content images a
1px
outline inset by
1px
. Use
oklch(0 0 0 / 0.1)
in light mode and
oklch(1 0 0 / 0.1)
in dark. Never use a palette near-black, a tinted neutral or the accent color, because the tint shows as a colored fringe on the image edge. Skip transparent artwork such as logos and illustrations. The recipe is in surfaces.md.
为内容图片添加
1px
outline,并向内偏移
1px
。浅色模式使用
oklch(0 0 0 / 0.1)
,深色模式使用
oklch(1 0 0 / 0.1)
。绝不要使用色板中的近黑色、带色偏的中性色或强调色,因为色偏会在图像边缘表现为彩色边缘。透明素材(如 logo 和插图)应跳过描边。相关 recipe 见 surfaces.md。

Transitions, not keyframes, for interactive state

交互状态使用 transition,而非 keyframes

Drive interactive state changes with CSS transitions or a motion library's springs, which retarget when the user reverses mid-flight. Keyframes run a fixed timeline and cannot reverse, so reserve them for sequences that run once. See animations.md.
使用 CSS transition 或动效库中的 spring 驱动交互状态变化;当用户在动画进行中反向操作时,这些方式可以重新设定目标。keyframes 按固定时间线执行,无法反向,因此仅用于只执行一次的序列。参见 animations.md。

Press scales to 0.96

按下时缩放至 0.96

A pressed button scales to
0.96
over
150ms
with
ease-out
, and a disabled one never scales. Give the button an opt-out where the motion would distract, through the component's existing variant API or a
static
prop. See recipes for CSS, Tailwind and Motion.
按钮在按下时应在
150ms
内以
ease-out
缩放至
0.96
,禁用状态下绝不缩放。如果动效会造成干扰,应通过组件现有的 variant API 或
static
prop 为按钮提供退出机制。参见 CSS、Tailwind 和 Motion 的 recipe。

High-frequency interactions get no animation

高频交互不添加动画

Keystrokes, row hovers and tab switches get instant feedback, or a transition of
150ms
or less on
opacity
or
background-color
. Reserve expressive motion for infrequent moments such as a view's first load, a success state or an empty state.
Every animated state change also leaves a static cue in the form of a color, icon or label. Motion is never the only feedback channel.
键盘输入、行悬停和标签页切换应即时反馈,或仅对
opacity
或
background-color
使用不超过
150ms
的 transition。表现力较强的动效应保留给低频场景,例如视图首次加载、成功状态或空状态。
每个带动画的状态变化还应保留静态提示,例如颜色、图标或标签。动效绝不能成为唯一的反馈通道。

Gate motion behind the reduced-motion preference

在 reduced-motion 偏好下限制动效

better-accessibility
owns the requirement, and no recipe here ships without it. Run movement, scale and blur only under
prefers-reduced-motion: no-preference
. Under reduced motion, replace them with an opacity cross-fade rather than removing the element instantly. Recipes for CSS, Tailwind and Motion are in animations.md.
此项要求由
better-accessibility
负责,本节任何 recipe 交付时都必须包含它。位移、缩放和模糊仅可在
prefers-reduced-motion: no-preference
下运行。在 reduced motion 下,应改用 opacity 交叉淡化,而不是瞬间移除元素。CSS、Tailwind 和 Motion 的 recipe 见 animations.md。

Stagger infrequent entrances by 100ms

低频入场动画间隔 100ms

Where sequence communicates hierarchy in a staged entrance, split the content into semantic chunks such as title, description and actions. Stagger them
100ms
apart, each entering with opacity,
4px
of blur and
12px
of
translateY
over
300ms
with
ease-out
. Never stagger routine interactions. See enter-exit.md.
当一个序列通过分阶段入场传达层级关系时,将内容拆分为标题、描述和操作等语义区块。各区块间隔
100ms
依次入场,每块使用 opacity、
4px
blur 和
12px
translateY
,持续
300ms
并采用
ease-out
。常规交互绝不采用错峰入场。参见 enter-exit.md。

Exits are shorter and smaller than enters

退场比入场更短、更小

Exit with opacity,
4px
of blur and a fixed
-12px
translateY
over
150ms
with
ease-out
, never the full container height. Slide fully out only where the destination carries meaning, such as a drawer closing. Remove the element instantly where motion adds no information. See enter-exit.md.
退场时使用 opacity、
4px
blur 和固定的
-12px
translateY
,持续
150ms
并采用
ease-out
;位移量绝不能达到容器的完整高度。仅当目标位置具有语义含义时,例如抽屉关闭,才让元素完全滑出。如果动效不提供任何信息,则应瞬间移除元素。参见 enter-exit.md。

Skip state animations on first render

首次渲染时跳过状态动画

Set
initial={false}
on an
AnimatePresence
that wraps a state swap, so the default state does not animate in on mount. Never set it around an intentional entrance such as a staggered hero. See animations.md.
对于包裹状态切换的
AnimatePresence
,设置
initial={false}
,使默认状态不会在挂载时执行入场动画。对于有意设计的入场(例如错峰显示 hero),绝不这样设置。参见 animations.md。

Cross-fade contextual icons with exact values

使用精确数值交叉淡化上下文图标

Where an icon swaps on an infrequent state change, such as play to pause or copy to copied, cross-fade it. Scale runs
0.25
to
1
, opacity
0
to
1
and blur
4px
to
0px
. With a motion library, use
{ type: "spring", duration: 0.3, bounce: 0 }
, and bounce is always
0
. Without one, keep both icons in the DOM and cross-fade over
300ms
with
cubic-bezier(0.2, 0, 0, 1)
.
A tab's icon swap and the actions a row reveals on hover fall under High-frequency interactions get no animation. Both recipes are in icon-transitions.md.
当图标在低频状态变化时切换,例如从播放切换到暂停,或从复制切换到已复制,应使用交叉淡化。scale 从
0.25
变为
1
,opacity 从
0
变为
1
,blur 从
4px
变为
0px
。使用动效库时,采用
{ type: "spring", duration: 0.3, bounce: 0 }
,且 bounce 始终为
0
。不使用动效库时,应让两个图标都保留在 DOM 中,并在
300ms
内以
cubic-bezier(0.2, 0, 0, 1)
交叉淡化。
标签页的图标切换,以及某一行在 hover 时显示的操作,均属于 高频交互不添加动画。两项 recipe 均见 icon-transitions.md。

Suppress transitions on theme switch

主题切换时抑制 transition

Disable every transition for the theme swap, force a style flush and restore transitions after the next frame. Otherwise every color transition fires at once and the switch smears. See the recipe.
主题切换时禁用所有 transition,强制刷新样式,并在下一帧后恢复 transition。否则所有颜色 transition 会同时触发,导致切换过程出现拖影。参见 recipe。

Transition only what changes

仅 transition 发生变化的属性

Name the exact properties, as in
transition-property: scale, opacity
, and never
all
. See performance.md.
明确指定具体属性,例如
transition-property: scale, opacity
,绝不使用
all
。参见 performance.md。

Name the animated property in will-change

在 will-change 中指明动画属性

Add
will-change
only after you see first-frame stutter, and name the property you animate:
scale
for
scale
,
transform
for
transform
. Limit it to transform properties,
opacity
and
filter
, and never use
all
. See performance.md.
只有在实际观察到首帧卡顿后才添加
will-change
,并指明要动画的属性:动画
scale
时写
scale
,动画
transform
时写
transform
。仅将其用于 transform 类属性、
opacity
和
filter
,绝不使用
all
。参见 performance.md。

Hover effects only on hover-capable pointers

仅对支持 hover 的指针设备应用悬停效果

Put hover-only styling behind
@media (hover: hover)
. On touch,
:hover
latches after a tap and reads as a stuck selected state. Tailwind 4's
hover:
variant already compiles under that query, and Tailwind 3's does not. Where a control draws its own pressed state, set
-webkit-tap-highlight-color: transparent
so the default gray flash does not double it.
仅悬停样式应放在
@media (hover: hover)
查询内。在触摸设备上,点击后
:hover
会保持激活,看起来像卡在选中状态。Tailwind 4 的
hover:
variant 已编译到该查询下,而 Tailwind 3 的没有。如果控件自行绘制按下状态,应设置
-webkit-tap-highlight-color: transparent
,避免默认灰色闪光与其叠加。

Contain scroll inside overlays

将滚动限制在浮层内部

Give scrollable dialogs, drawers, menus and side panels
overscroll-behavior: contain
, so scrolling past their end never scrolls the page behind.
为可滚动的对话框、抽屉、菜单和侧边面板设置
overscroll-behavior: contain
,使滚动越过其末端时绝不会带动后方页面滚动。

Match icon stroke to text weight

图标描边粗细与文字字重匹配

An icon's rendered stroke tracks the weight of the text beside it, from
1.5px
at 400 to
2.5px
at 700. Use one icon library per surface. The table, sizing and grid rules are in icons.md.
图标渲染后的描边粗细应与旁边文字的字重一致:字重为 400 时为
1.5px
,字重为 700 时为
2.5px
。每个界面层只使用一套 icon library。表格、尺寸和网格规则见 icons.md。

One SVG, recolored per state

单一 SVG,按状态重新着色

Icons use
currentColor
and take hover, selected and disabled states from CSS color and opacity, never from separate assets. Outline is the default variant, and fill marks the active state. Under
dir="rtl"
, mirror only icons whose meaning follows reading direction. See icons.md.
图标使用
currentColor
,并通过 CSS color 和 opacity 呈现 hover、selected 和 disabled 状态,绝不使用单独的素材资源。outline 是默认变体,fill 用于标记 active 状态。在
dir="rtl"
下,仅镜像含义随阅读方向变化的图标。参见 icons.md。

Before you finish

完成前检查

DetectionFix
Padded parent and child with the same
rounded-*
or
border-radius
Add the padding to the outer radius
box-shadow: 0 0 0 1px
ring with no
border
beside it
border: 1px solid transparent
outline-slate-*
,
outline-zinc-*
or a hex outline color on
img
outline-black/10
and
dark:outline-white/10
transition-all
or
transition: all
Name the changing properties
animation:
set on
:hover
,
.open
or another toggled class
A transition on the same properties
active:scale-95
,
scale-90
or
whileTap
below
0.96
0.96
:active
scale with no
:disabled
exclusion
enabled:active:
or
:not(:disabled):active
bounce:
set to anything but
0
bounce: 0
opacity: 0
in a base rule that only an
animation
reveals
Hidden state in the
from
keyframe,
animation-fill-mode: both
translate
,
scale
,
filter
or
motion.*
animation with no
prefers-reduced-motion
,
motion-safe:
or
MotionConfig
guard
Gate it and cross-fade opacity under reduced motion
Color transitions plus a theme toggle with no
disableTransitionOnChange
or override
The theme-switch suppression recipe
will-change: all
, or
will-change
on an element that never animates
Name the animated property or delete it
fill="#..."
or
stroke="#..."
inside an icon SVG
currentColor
Plain-CSS
:hover
or Tailwind 3
hover:
outside
@media (hover: hover)
Wrap it in the query
overflow: auto
or
overflow-y-auto
on a dialog, drawer or menu with no
overscroll-behavior
overscroll-behavior: contain
检测项修复方式
带 padding 的父级和子级使用相同的
rounded-*
或 border-radius
将 padding 加到外层圆角上
使用
box-shadow: 0 0 0 1px
环,但旁无
border
border: 1px solid transparent
img
上使用
outline-slate-*
、
outline-zinc-*
或十六进制 outline 颜色
outline-black/10
和
dark:outline-white/10
transition-all
或
transition: all
明确指定发生变化的属性
在
:hover
、
.open
或其他切换类上设置
animation:
对相同属性使用 transition
active:scale-95
、
scale-90
或
whileTap
低于
0.96
0.96
:active
缩放未排除
:disabled
enabled:active:
或
:not(:disabled):active
bounce:
设置为
0
以外的值
bounce: 0
基础规则中包含
opacity: 0
,且仅由
animation
显现
在
from
keyframe 中设置隐藏状态,并使用
animation-fill-mode: both
translate
、
scale
、
filter
或
motion.*
动画缺少
prefers-reduced-motion
、
motion-safe:
或
MotionConfig
防护
为其设置限制条件,并在 reduced motion 下使用 opacity 交叉淡化
颜色 transition 与主题切换并存,但未使用
disableTransitionOnChange
或覆盖机制
使用主题切换抑制 recipe
will-change: all
,或在从不动画的元素上设置
will-change
指明动画属性或删除
will-change
图标 SVG 内使用
fill="#..."
或
stroke="#..."
currentColor
纯 CSS
:hover
或 Tailwind 3 的
hover:
未置于
@media (hover: hover)
内
将其包裹在该查询中
对话框、抽屉或菜单使用
overflow: auto
或
overflow-y-auto
,但未设置
overscroll-behavior
overscroll-behavior: contain

Reporting

报告

Severity.
HIGH
breaks an interaction, as a keyframe toggle that cannot reverse or a hover state stuck on touch does. Two of
better-interface
's escalation triggers land here and are
HIGH
on sight. One is motion that ignores
prefers-reduced-motion
, and the other is a state change carried by motion alone.
MEDIUM
is a visible inconsistency in surfaces, icons or motion.
LOW
is isolated polish.
Verification. Without a browser, read every state the component defines from the code, such as hover, pressed, selected, loading and empty, with its durations and easings. With one, walk each state and replay motion at 10% speed in the browser's Animations panel. Report every check you could not run as
Not verified
.
Format. Group findings under the principle each violates, ordered by severity, one row per root cause listing every location it appears in:
SeverityLocationBeforeAfterWhy
Location
is
path/to/file:line
.
Why
names the principle and the user impact.
End with
Block
when any
HIGH
remains,
Approve
otherwise, leaving the rest in the table as work to do. Never
Approve
coverage you did not inspect. With nothing to report, state "No actionable UI-polish findings" and report verification.
严重程度。
HIGH
会破坏交互,例如无法反向的 keyframe 切换,或在触摸设备上卡住的 hover 状态。
better-interface
的两项升级触发条件也属于此类,看到即可判定为
HIGH
:一项是忽略
prefers-reduced-motion
的动效,另一项是仅依靠动效传达的状态变化。
MEDIUM
表示界面层、图标或动效中存在可见的不一致。
LOW
表示孤立的打磨问题。
验证。 如果没有浏览器,应从代码中读取组件定义的每个状态,例如 hover、pressed、selected、loading 和 empty,并检查其 duration 和 easing。如果有浏览器,则逐一走查每个状态,并在浏览器的 Animations 面板中以 10% 速度重放动效。每项未能执行的检查都应报告为“Not verified”。
格式。 按每项问题所违反的原则分组,并按严重程度排序;每个根因占一行,并列出其出现的所有位置:
严重程度位置修改前修改后原因
Location
为
path/to/file:line
。
Why
应说明所违反的原则及其对用户的影响。
只要仍有任何
HIGH
问题,最后就以
Block
结束;否则以
Approve
结束,并将其余问题保留在表中作为待办事项。绝不能对未检查的范围给出
Approve
。如果没有可报告的问题,请写明“No actionable UI-polish findings”,并报告验证情况。