Button
- 组件说明:Primary 按钮用于页面的单一主要操作,独立使用,不与其他按钮并列。
- 尺寸基线:Small=32px、Medium=40px、Large=48px、Icon Only=28px。
- 实现约定:
ui/button只保留结构骨架,design 层统一接管全部视觉 token。 - Figma 规范
变体
8 种视觉变体,适配不同语义层级。
结果
Loading...
实时编辑器
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}> <Button variant="primary">Primary</Button> <Button variant="secondary">Secondary</Button> <Button variant="tertiary">Tertiary</Button> <Button variant="quaternary">Quaternary</Button> <Button variant="destructive">Destructive</Button> <Button variant="destructive-outline">Destructive Outline</Button> <Button variant="link-color">Link Color</Button> <Button variant="link-gray">Link Gray</Button> </div>
| 样式 | 说明 |
|---|---|
| Primary | 实心深色背景 + 反白文字,强调主操作 |
| Secondary | 白色背景带边框,次于 Primary |
| Tertiary | 透明无边框,hover 显示次级背景色 |
| Quaternary | 透明无边框,hover 显示 tinted 灰色背景,视觉最轻 |
| Destructive | 错误色实心背景,强调危险操作 |
| Destructive Outline | 错误色描边 + 错误色文字,危险操作的弱强调态 |
| Link Color | 品牌色文本链接样式 |
| Link Gray | 灰色文本链接样式,hover 变为主色 |
状态
5 种交互状态:Default / Hover / Clicked / Loading / Disabled。
结果
Loading...
实时编辑器
<div style={{ display: 'flex', flexDirection: 'column', gap: 24 }}> <div style={{ display: 'flex', gap: 32, alignItems: 'center' }}> <div style={{ width: 120 }}> <span style={{ fontSize: 12, color: '#666', fontWeight: 600 }}>State</span> </div> <div style={{ display: 'flex', gap: 32 }}> {['primary', 'secondary', 'tertiary', 'quaternary', 'destructive', 'destructive-outline', 'link-color', 'link-gray'].map((variant) => ( <div key={variant} style={{ width: 80, textAlign: 'center' }}> <span style={{ fontSize: 12, color: '#666', fontWeight: 600, textTransform: 'capitalize' }}> {variant.replace('-', ' ')} </span> </div> ))} </div> </div> {['Default', 'Hover', 'Clicked', 'Loading', 'Disabled'].map((state) => ( <div key={state} style={{ display: 'flex', gap: 32, alignItems: 'center' }}> <div style={{ width: 120 }}> <span style={{ fontSize: 12, color: '#999' }}>{state}</span> </div> <div style={{ display: 'flex', gap: 32 }}> {['primary', 'secondary', 'tertiary', 'quaternary', 'destructive', 'destructive-outline', 'link-color', 'link-gray'].map((variant) => ( <div key={variant} style={{ width: 80, display: 'flex', justifyContent: 'center' }}> {state === 'Default' && <Button variant={variant}>Button</Button>} {state === 'Hover' && ( <> {variant === 'primary' && <Button variant={variant} className="bg-(--Labels-Secondary)">Button</Button>} {variant === 'secondary' && <Button variant={variant} className="bg-(--Grays-Gray-1)">Button</Button>} {variant === 'tertiary' && <Button variant={variant} className="bg-(--Grays-Gray-1)">Button</Button>} {variant === 'quaternary' && <Button variant={variant} className="bg-(--Grays-Tinted-Default)">Button</Button>} {variant === 'destructive' && <Button variant={variant} className="bg-[color-mix(in_srgb,var(--Labels-Error)_90%,white)]">Button</Button>} {variant === 'destructive-outline' && <Button variant={variant} className="bg-(--Labels-Error)/10">Button</Button>} {variant === 'link-color' && <Button variant={variant} className="underline">Button</Button>} {variant === 'link-gray' && <Button variant={variant} className="underline text-(--Labels-Primary)">Button</Button>} </> )} {state === 'Clicked' && ( <> {variant === 'primary' && <Button variant={variant} className="bg-(--Labels-Secondary) ring-1 ring-inset ring-(--Labels-Primary)">Button</Button>} {variant === 'secondary' && <Button variant={variant} className="bg-(--Grays-Gray-2)">Button</Button>} {variant === 'tertiary' && <Button variant={variant} className="bg-(--Grays-Gray-2)">Button</Button>} {variant === 'quaternary' && <Button variant={variant} className="bg-(--Grays-Tinted-Emphasized)">Button</Button>} {variant === 'destructive' && <Button variant={variant} className="bg-[color-mix(in_srgb,var(--Labels-Error)_90%,black)]">Button</Button>} {variant === 'destructive-outline' && <Button variant={variant} className="bg-(--Labels-Error)/20">Button</Button>} {variant === 'link-color' && <Button variant={variant} className="underline">Button</Button>} {variant === 'link-gray' && <Button variant={variant} className="underline text-(--Labels-Primary)">Button</Button>} </> )} {state === 'Loading' && <Button variant={variant} loading>Button</Button>} {state === 'Disabled' && <Button variant={variant} disabled>Button</Button>} </div> ))} </div> </div> ))} </div>
图标按钮
size="icon" 时渲染为 28×28 方形,必须设置 aria-label 以保证无障碍。
结果
Loading...
实时编辑器
<div style={{ display: 'flex', gap: 12, alignItems: 'center', flexWrap: 'wrap' }}> <Button size="icon" variant="primary" aria-label="Add"> <Plus size={16} /> </Button> <Button size="icon" variant="secondary" aria-label="Add"> <Plus size={16} /> </Button> <Button size="icon" variant="tertiary" aria-label="Add"> <Plus size={16} /> </Button> <Button size="icon" variant="quaternary" aria-label="Add"> <Plus size={16} /> </Button> <Button size="icon" variant="destructive" aria-label="Add"> <Plus size={16} /> </Button> <Button size="icon" variant="destructive-outline" aria-label="Add"> <Plus size={16} /> </Button> <Button size="icon" variant="link-color" aria-label="Add"> <Plus size={16} /> </Button> <Button size="icon" variant="link-gray" aria-label="Add"> <Plus size={16} /> </Button> </div>
Icon Only 的 Tertiary / Quaternary 交互反馈与文字按钮不同:
- Tertiary:Hover / Clicked 背景使用 Tinted 系列(
Grays/Tinted/Default、Grays/Tinted/Emphasized),而非文字按钮的 Gray 系列。 - Quaternary:移除背景反馈,仅通过 icon 颜色变化体现交互——Default
Labels/Tertiary→ HoverLabels/Primary→ ClickedLabels/Secondary。
尺寸规范
结果
Loading...
实时编辑器
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 24 }}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}> <Button variant="secondary" size="lg"> Large </Button> <span style={{ fontSize: 12, color: '#999' }}>48px</span> </div> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}> <Button variant="secondary" size="default"> Medium </Button> <span style={{ fontSize: 12, color: '#999' }}>40px</span> </div> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}> <Button variant="secondary" size="sm"> Small </Button> <span style={{ fontSize: 12, color: '#999' }}>32px</span> </div> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}> <Button variant="secondary" size="icon" aria-label="Add"> <Plus size={16} /> </Button> <span style={{ fontSize: 12, color: '#999' }}>28px</span> </div> </div>
| 维度 | Large | Medium | Small | Icon Only |
|---|---|---|---|---|
| 高度 | 48px | 40px | 32px | 28px |
| 最小宽度 | 100px | 100px | 70px | 28px |
| 水平内边距 | 24px | 24px | 16px | 0 |
| 圆角 | 5px | 5px | 5px | 5px |
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
children | ReactNode | - | 按钮内容 |
variant | 'primary' | 'secondary' | 'tertiary' | 'quaternary' | 'destructive' | 'destructive-outline' | 'link-color' | 'link-gray' | 'primary' | 视觉变体 |
size | 'default' | 'sm' | 'lg' | 'icon' | 'default' | 尺寸:sm=32px、default=40px、lg=48px、icon=28px |
loading | boolean | false | 加载状态,显示 Spinner 并禁用交互 |
disabled | boolean | false | 禁用状态 |
onClick | MouseEventHandler | - | 点击回调 |
aria-label | string | - | 无障碍标签,size="icon" 时必填 |