按钮组件设计详解
按钮是 UI 中最重要的交互元素。优秀的按钮设计能够指导用户行为。
按钮类型
Primary Button(主按钮)
.btn-primary {
background-color: #0066cc;
color: #ffffff;
padding: 12px 24px;
border: none;
border-radius: 4px;
font-weight: 600;
font-size: 16px;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-primary:hover {
background-color: #0052a3;
box-shadow: 0 4px 12px rgba(0, 102, 204, 0.2);
}
.btn-primary:active {
background-color: #003d7a;
transform: scale(0.98);
}
.btn-primary:disabled {
background-color: #cccccc;
cursor: not-allowed;
opacity: 0.6;
}
Secondary Button(次按钮)
.btn-secondary {
background-color: transparent;
color: #0066cc;
border: 2px solid #0066cc;
padding: 10px 22px;
border-radius: 4px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-secondary:hover {
background-color: rgba(0, 102, 204, 0.1);
}
.btn-secondary:active {
background-color: rgba(0, 102, 204, 0.2);
}
Danger Button(危险按钮)
.btn-danger {
background-color: #cc0000;
color: #ffffff;
padding: 12px 24px;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
}
.btn-danger:hover {
background-color: #990000;
box-shadow: 0 4px 12px rgba(204, 0, 0, 0.2);
}
交互状态
Loading 状态
import { useState } from 'react';
function Button({ children, onClick, loading, ...props }) {
const [isLoading, setIsLoading] = useState(false);
const handleClick = async () => {
setIsLoading(true);
try {
await onClick();
} finally {
setIsLoading(false);
}
};
return (
<button
onClick={handleClick}
disabled={isLoading || loading}
aria-busy={isLoading || loading}
{...props}
>
{isLoading ? (
<>
<span className="spinner" aria-hidden="true"></span>
{children}
</>
) : (
children
)}
</button>
);
}
Disabled 状态
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
background-color: #cccccc;
color: #999999;
}
/* 禁用状态下隐藏指针光标 */
.btn:disabled:hover {
box-shadow: none;
transform: none;
}
Focus 状态
.btn:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1),
0 0 0 5px #0066cc;
}
/* 键盘导航焦点 */
.btn:focus-visible {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
按钮大小
/* 小按钮 */
.btn-sm {
padding: 6px 12px;
font-size: 12px;
min-height: 32px;
min-width: 32px;
}
/* 中等按钮(默认) */
.btn-md {
padding: 12px 24px;
font-size: 16px;
min-height: 44px;
min-width: 44px;
}
/* 大按钮 */
.btn-lg {
padding: 16px 32px;
font-size: 18px;
min-height: 56px;
min-width: 56px;
}
/* 全宽按钮 */
.btn-block {
width: 100%;
display: block;
}
无障碍性
<!-- 语义正确 -->
<button type="submit" aria-label="提交表单">
提交
</button>
<!-- 加载状态 -->
<button aria-busy="true" disabled>
<span aria-hidden="true" class="spinner"></span>
加载中...
</button>
<!-- 图标按钮 -->
<button aria-label="关闭">
<svg aria-hidden="true" width="24" height="24">
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
<!-- 切换按钮 -->
<button aria-pressed="false" aria-label="点赞">
♥
</button>
完整组件示例
const Button = React.forwardRef((
{
children,
variant = 'primary',
size = 'md',
loading = false,
disabled = false,
icon,
className,
...props
},
ref
) => {
return (
<button
ref={ref}
className={`btn btn-${variant} btn-${size} ${className}`}
disabled={disabled || loading}
aria-busy={loading}
{...props}
>
{icon && <span className="btn-icon" aria-hidden="true">{icon}</span>}
{loading ? (
<>
<span className="spinner" aria-hidden="true"></span>
{children}
</>
) : (
children
)}
</button>
);
});
Button.displayName = 'Button';
最佳实践
✅ 应该做的事:
- 最小触摸目标 44x44px
- 清晰的视觉反馈
- 使用语义 HTML
<button> - 提供加载状态反馈
- 支持键盘导航
❌ 不应该做的事:
- 使用
<div>模拟按钮 - 隐藏焦点指示器
- 过多的按钮样式
- 忽视禁用状态
- 使用
<a>代替按钮
测试清单
- 在各种浏览器中测试
- 验证键盘导航
- 检查色彩对比度
- 测试触摸设备
- 屏幕阅读器兼容性


