button-states
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseButton and Interactive Element States
按钮与交互元素状态
Every interactive component must have a complete, visually distinct state for each interaction mode. Missing or ambiguous states make the UI feel unfinished and reduce user confidence.
每个交互组件都必须为每种交互模式定义一个完整、视觉区分度清晰的状态。缺失或模糊的状态会让UI显得不够完善,降低用户的使用信心。
The Six States
六种状态
| State | Trigger | Visual signal |
|---|---|---|
| Rest | Default | Base colour, cursor: pointer |
| Hover | Mouse over | Slightly darker, subtle background shift |
| Active / Pressed | Mouse down / tap | Noticeably darker, slight scale-down |
| Focus | Keyboard navigation | Visible focus ring, no change to fill |
| Disabled | Not available | Low contrast, cursor: not-allowed, no interaction |
| Loading | Async action in progress | Spinner or pulse, non-interactive |
| 状态 | 触发条件 | 视觉信号 |
|---|---|---|
| 默认态(Rest) | 默认状态 | 基础颜色,光标:pointer |
| 悬停态(Hover) | 鼠标悬停 | 颜色略深,背景轻微变化 |
| 激活/按压态(Active / Pressed) | 鼠标按下/点击 | 颜色明显加深,轻微缩小 |
| 聚焦态(Focus) | 键盘导航 | 可见的聚焦环,填充色无变化 |
| 禁用态(Disabled) | 不可用 | 低对比度,光标:not-allowed,无法交互 |
| 加载态(Loading) | 异步操作进行中 | 加载动画或脉冲效果,无法交互 |
Deriving State Colours Algorithmically
通过算法衍生状态颜色
State colours are not chosen independently — they are derived from the base colour by adjusting lightness in HSL. This guarantees coherence across the entire palette.
base: hsl(H, S%, L%)
hover: hsl(H, S%, L% - 8%) ← darken 8%
active: hsl(H, S%, L% - 14%) ← darken 14%状态颜色并非独立选择——它们通过调整HSL中的亮度值从基础色衍生而来,这能确保整个调色板的一致性。
base: hsl(H, S%, L%)
hover: hsl(H, S%, L% - 8%) ← 加深8%
active: hsl(H, S%, L% - 14%) ← 加深14%Example: primary button #635BFF
(hsl 243, 100%, 68%)
#635BFF示例:主按钮 #635BFF
(hsl 243, 100%, 68%)
#635BFFcss
.btn-primary {
background: hsl(243, 100%, 68%); /* rest #635BFF */
}
.btn-primary:hover {
background: hsl(243, 100%, 60%); /* hover #4A40FF */
}
.btn-primary:active {
background: hsl(243, 100%, 54%); /* active #3429FF */
}For light buttons on dark backgrounds, invert the logic — lighten on hover instead of darkening.
css
.btn-primary {
background: hsl(243, 100%, 68%); /* rest #635BFF */
}
.btn-primary:hover {
background: hsl(243, 100%, 60%); /* hover #4A40FF */
}
.btn-primary:active {
background: hsl(243, 100%, 54%); /* active #3429FF */
}对于深色背景上的浅色按钮,需反转逻辑——悬停时提亮而非加深颜色。
Secondary / outlined buttons
次要/轮廓按钮
css
.btn-secondary {
background: transparent;
border: 1px solid var(--color-border);
color: var(--color-text);
}
.btn-secondary:hover {
background: var(--color-grey-100); /* subtle fill */
border-color: var(--color-grey-300);
}
.btn-secondary:active {
background: var(--color-grey-200);
}css
.btn-secondary {
background: transparent;
border: 1px solid var(--color-border);
color: var(--color-text);
}
.btn-secondary:hover {
background: var(--color-grey-100); /* subtle fill */
border-color: var(--color-grey-300);
}
.btn-secondary:active {
background: var(--color-grey-200);
}Focus State
聚焦态
Focus is a keyboard navigation requirement (WCAG 2.2). It must be visible and must not rely on the hover style alone — keyboard users do not trigger hover.
css
.btn:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 3px;
}- Use , not
outline, for focus rings —box-shadowrespectsoutlinein modern browsers and does not affect layoutborder-radius - gives the ring breathing room from the component edge
outline-offset: 2–4px - Never use without a replacement focus style
outline: none
聚焦是键盘导航的要求(WCAG 2.2)。聚焦状态必须可见,且不能仅依赖悬停样式——键盘用户不会触发悬停。
css
.btn:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 3px;
}- 使用而非
outline实现聚焦环——现代浏览器中box-shadow会遵循outline,且不会影响布局border-radius - 让聚焦环与组件边缘保持一定间距
outline-offset: 2–4px - 禁止在未提供替代聚焦样式的情况下使用
outline: none
Disabled State
禁用态
css
.btn:disabled,
.btn[aria-disabled="true"] {
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
}- Disabled elements are exempt from WCAG contrast requirements — low opacity is correct and intentional
- Use to prevent click events even if JS is bypassed
pointer-events: none - Do not change the shape or size of a disabled button — only colour and cursor change
css
.btn:disabled,
.btn[aria-disabled="true"] {
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
}- 禁用元素不受WCAG对比度要求限制——低透明度是正确且有意的设计
- 使用防止即使JS失效时仍触发点击事件
pointer-events: none - 禁用按钮的形状或尺寸不应改变——仅调整颜色和光标
Loading State
加载态
When a button triggers an async action, replace the label with a spinner and prevent re-submission.
css
.btn--loading {
pointer-events: none;
cursor: wait;
opacity: 0.7;
}- Keep the button width stable during loading — avoid layout shift when label is replaced by spinner
- Return to rest state on completion (success or error)
- For long-running operations, pair with a status message — a spinner alone does not tell the user what is happening
当按钮触发异步操作时,将标签替换为加载动画,并防止重复提交。
css
.btn--loading {
pointer-events: none;
cursor: wait;
opacity: 0.7;
}- 加载过程中保持按钮宽度稳定——避免标签被替换为加载动画时出现布局偏移
- 操作完成(成功或失败)后恢复为默认态
- 对于长时间运行的操作,需搭配状态提示信息——仅加载动画无法告知用户当前正在进行的操作
Scale on Active (Optional)
激活时缩放(可选)
A subtle scale-down on press adds physical feedback — borrowed from Disney's squash principle.
css
.btn:active {
transform: scale(0.97);
transition: transform 80ms ease-out;
}Keep the scale value between . Below feels like the button is breaking.
0.95–0.980.95按压时轻微缩小能提供物理反馈——灵感来自迪士尼的挤压原则。
css
.btn:active {
transform: scale(0.97);
transition: transform 80ms ease-out;
}缩放值应保持在之间。低于会让按钮看起来像是“断裂”了。
0.95–0.980.95Complete Button CSS Reference
完整按钮CSS参考
css
.btn {
cursor: pointer;
background: var(--color-primary);
color: white;
border-radius: var(--radius-button);
padding: var(--component-padding-y-md) var(--component-padding-x-md);
height: var(--component-height-md);
border: none;
transition: background 120ms ease-out, transform 80ms ease-out;
}
.btn:hover { background: var(--color-primary-hover); }
.btn:active { background: var(--color-primary-active); transform: scale(0.97); }
.btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }
.btn:disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
.btn.btn--loading { opacity: 0.7; cursor: wait; pointer-events: none; }css
.btn {
cursor: pointer;
background: var(--color-primary);
color: white;
border-radius: var(--radius-button);
padding: var(--component-padding-y-md) var(--component-padding-x-md);
height: var(--component-height-md);
border: none;
transition: background 120ms ease-out, transform 80ms ease-out;
}
.btn:hover { background: var(--color-primary-hover); }
.btn:active { background: var(--color-primary-active); transform: scale(0.97); }
.btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }
.btn:disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
.btn.btn--loading { opacity: 0.7; cursor: wait; pointer-events: none; }Review Checklist
检查清单
- Does every interactive element have all six states defined?
- Are hover and active colours derived from the base by lightness adjustment (not chosen arbitrarily)?
- Is focus state visible and using (not removed)?
outline - Is disabled state low-opacity with ?
cursor: not-allowed - Does loading state prevent re-submission?
- Are transition durations 80–150ms — not instant, not slow?
- Does appear on all interactive elements at rest?
cursor: pointer
- 每个交互元素是否都定义了全部六种状态?
- 悬停和激活颜色是否通过调整亮度从基础色衍生(而非随意选择)?
- 聚焦状态是否可见且使用(未被移除)?
outline - 禁用状态是否为低透明度并设置?
cursor: not-allowed - 加载状态是否防止重复提交?
- 过渡时长是否在80–150ms之间——既不突兀也不缓慢?
- 所有交互元素在默认态下是否显示?
cursor: pointer