跳到主要内容

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/DefaultGrays/Tinted/Emphasized),而非文字按钮的 Gray 系列。
  • Quaternary:移除背景反馈,仅通过 icon 颜色变化体现交互——Default Labels/Tertiary → Hover Labels/Primary → Clicked Labels/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>
维度LargeMediumSmallIcon Only
高度48px40px32px28px
最小宽度100px100px70px28px
水平内边距24px24px16px0
圆角5px5px5px5px

Props

属性类型默认值说明
childrenReactNode-按钮内容
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
loadingbooleanfalse加载状态,显示 Spinner 并禁用交互
disabledbooleanfalse禁用状态
onClickMouseEventHandler-点击回调
aria-labelstring-无障碍标签,size="icon" 时必填