Skip to Content
@anker-in/headless-ui 2.0 is released 🎉

Button 按钮

可点击的交互式按钮组件,支持多种样式、大小、状态和加载效果。按钮是用户界面中最常用的交互元素之一。

何时使用

  • 需要触发一个业务逻辑操作时(如提交表单、删除数据、打开对话框)
  • 需要引导用户完成主要操作时(如”立即购买”、“注册账号”)
  • 需要在页面之间导航时(使用 as="a" 属性)
  • 需要展示加载状态以提供视觉反馈时

快速开始

安装

pnpm add @anker-in/headless-ui

基础用法

import { Button } from '@anker-in/headless-ui' export default function App() { return <Button>立即购买</Button> }

演示

Button 组件演示

按钮组件支持多种变体、尺寸和状态,可满足不同场景的交互需求

基本用法

Button 组件的基本用法示例

100% (1920px)

变体(Variants)

Button 组件提供 4 种视觉变体,适用于不同的视觉层级和使用场景。

Primary - 主要按钮

用于页面中最重要的操作,通常一个页面只有一个主要按钮。

<Button variant="primary">主要操作</Button>

Secondary - 次要按钮

用于次要操作或与主要按钮配合使用。

<Button variant="secondary">次要操作</Button>

用于不太重要的操作或文本内的链接样式按钮。自动带有右侧箭头图标。

<Button variant="link">了解更多</Button>

Ghost - 幽灵按钮 ⚠️

Ghost 变体即将废弃,请使用 secondary 变体代替。

<Button variant="ghost">幽灵按钮</Button>

尺寸(Sizes)

Button 组件提供多种尺寸选项以适应不同的 UI 密度和设备。

<Button size="sm">小尺寸</Button> <Button size="base">基础尺寸</Button> <Button size="lg">大尺寸(默认)</Button> <Button size="icon">图标按钮</Button>

响应式尺寸: lg 尺寸在超大屏幕(≥1920px)上会自动增大,以提供更好的视觉体验。


状态(States)

加载状态

显示加载指示器,同时禁用按钮交互。按钮会保持原始宽度以避免布局跳动。

<Button loading>加载中...</Button>

自定义 Spinner

<Button loading spinner={ <svg className="animate-spin" width="16" height="16"> {/* 自定义 SVG */} </svg> } > 处理中 </Button>

禁用状态

禁用按钮交互,显示禁用样式。

<Button disabled>禁用按钮</Button>

Hover 动画效果

支持滑动动画效果(仅 primarysecondary 变体)。

<Button hoverEffect="slide">Hover 滑动效果</Button>

Props API

Button Props

属性类型默认值必需说明
variant'primary' | 'secondary' | 'link' | 'ghost''primary'按钮变体
size'sm' | 'base' | 'lg' | 'icon''lg'按钮尺寸
hoverEffect'none' | 'slide''none'Hover 动画效果(仅 primary/secondary)
loadingbooleanfalse是否显示加载状态
disabledbooleanfalse是否禁用按钮
as'button' | 'a''button'渲染为按钮或链接
hrefstring-链接地址(当 as="a" 时使用)
spinnerReactNode-自定义加载图标
iconClassNamestring-图标的自定义类名
childrenReactNode-按钮内容
classNamestring-自定义 CSS 类名
onClick(e: MouseEvent) => void-点击事件回调

继承的 Props

Button 组件继承所有原生 <button> 元素的属性(如 type, name, value 等)。


类型定义

import { type VariantProps } from 'class-variance-authority' export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> { /** 设置按钮载入状态 */ loading?: boolean /** 设置按钮失效状态 */ disabled?: boolean /** 定义渲染的元素类型 */ as?: 'button' | 'a' /** 链接地址(当 as='a' 时) */ href?: string /** 图标类名 */ iconClassName?: string /** 自定义 loading 图标 */ spinner?: React.ReactNode /** Hover 动画效果 */ hoverEffect?: 'none' | 'slide' } const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( ({ variant, size, loading, disabled, children, ...props }, ref) => { // 组件实现 } ) Button.displayName = 'Button' export default Button

使用示例

基础示例

最简单的按钮用法。

import { Button } from '@anker-in/headless-ui' export default function Example1() { return ( <Button onClick={() => alert('clicked!')}> 立即购买 </Button> ) }

带图标的按钮

在按钮中添加图标以增强视觉表达。

import { Button } from '@anker-in/headless-ui' import { ShoppingCartIcon } from '@/icons' export default function Example2() { return ( <Button> <ShoppingCartIcon className="mr-2" /> 加入购物车 </Button> ) }

Link 变体自动图标: variant="link" 会自动在右侧添加箭头图标,无需手动添加。

作为链接使用

使用 as="a" 属性将按钮渲染为链接,适用于页面导航。

import { Button } from '@anker-in/headless-ui' export default function Example3() { return ( <Button as="a" href="/products" variant="link"> 浏览全部产品 </Button> ) }

异步操作与加载状态

展示异步操作的加载状态。

import { useState } from 'react' import { Button } from '@anker-in/headless-ui' export default function Example4() { const [loading, setLoading] = useState(false) const handleSubmit = async () => { setLoading(true) try { await fetch('/api/submit', { method: 'POST' }) alert('提交成功!') } catch (error) { alert('提交失败') } finally { setLoading(false) } } return ( <Button loading={loading} onClick={handleSubmit}> 提交表单 </Button> ) }

表单中使用

在表单中使用不同类型的按钮。

import { Button } from '@anker-in/headless-ui' export default function Example5() { const handleSubmit = (e: React.FormEvent) => { e.preventDefault() // 处理表单提交 } return ( <form onSubmit={handleSubmit}> <input type="email" required /> <div className="mt-4 flex gap-3"> <Button type="submit" variant="primary"> 提交 </Button> <Button type="button" variant="secondary" onClick={() => console.log('取消')}> 取消 </Button> <Button type="reset" variant="link"> 重置 </Button> </div> </form> ) }

按钮组合

多个按钮组合使用时的视觉层级处理。

import { Button } from '@anker-in/headless-ui' export default function Example6() { return ( <div className="flex items-center gap-4"> {/* 主要操作 */} <Button variant="primary" size="lg"> 立即购买 </Button> {/* 次要操作 */} <Button variant="secondary" size="lg"> 加入购物车 </Button> {/* 辅助操作 */} <Button variant="link" size="lg"> 了解更多 </Button> </div> ) }

可访问性

键盘支持

按键说明
Enter激活按钮
Space激活按钮
Tab移动焦点到下一个可聚焦元素
Shift + Tab移动焦点到上一个可聚焦元素

ARIA 属性

Button 组件自动处理以下可访问性属性:

  • 使用 aria-hidden 隐藏视觉占位内容(加载状态时)
  • 使用 VisuallyHidden 确保加载状态下屏幕阅读器仍能读取按钮文本
  • 自动设置 disabled 属性(禁用和加载状态)

推荐的 ARIA 用法

{/* 图标按钮必须提供 aria-label */} <Button size="icon" aria-label="搜索"> <SearchIcon /> </Button> {/* 动态内容需要 aria-live */} <Button aria-live="polite" loading={loading}> {loading ? '处理中...' : '提交'} </Button>

最佳实践

确保足够的点击区域

按钮的最小点击区域应为 44x44px(移动端)以符合 WCAG 2.1 标准。组件默认已满足此要求。

提供清晰的按钮文本

使用描述性的按钮文本,避免使用模糊的词汇如”点击这里”、“提交”。

{/* ❌ 不推荐 */} <Button>提交</Button> {/* ✅ 推荐 */} <Button>提交订单</Button>

使用视觉焦点指示器

组件已内置焦点环(focus-visible:ring-2),确保键盘用户能清楚看到当前焦点。

合理使用加载状态

在异步操作时始终显示加载状态,避免用户重复点击。

<Button loading={isSubmitting} disabled={isSubmitting} onClick={handleSubmit} > {isSubmitting ? '提交中...' : '提交订单'} </Button>

测试屏幕阅读器

使用 VoiceOver(macOS)、NVDA(Windows)或 JAWS 测试按钮的可访问性。

避免仅用颜色区分状态

除了颜色变化,还应使用文本、图标等方式传达按钮状态。


最佳实践

✅ 推荐做法

  • 一个页面一个主要按钮: 避免使用多个 primary 按钮造成视觉混乱
<div> <Button variant="primary">立即购买</Button> <Button variant="secondary">加入购物车</Button> </div>
  • 异步操作显示加载状态: 提供清晰的视觉反馈
<Button loading={loading} disabled={loading}> {loading ? '处理中...' : '提交订单'} </Button>
  • 使用语义化的按钮类型: 在表单中使用正确的 type 属性
<Button type="submit">提交</Button> <Button type="button" onClick={handleCancel}>取消</Button> <Button type="reset">重置</Button>
  • 导航使用链接样式: 页面跳转使用 as="a"variant="link"
<Button as="a" href="/products" variant="link"> 查看全部产品 </Button>

❌ 避免做法

  • 不要在加载时允许交互: 必须同时设置 loadingdisabled
{/* ❌ 错误:未禁用按钮 */} <Button loading={loading} onClick={handleClick}> 提交 </Button> {/* ✅ 正确:同时禁用交互 */} <Button loading={loading} disabled={loading} onClick={handleClick}> 提交 </Button>
  • 不要省略图标按钮的标签: 图标按钮必须有 aria-label
{/* ❌ 错误 */} <Button size="icon"> <SearchIcon /> </Button> {/* ✅ 正确 */} <Button size="icon" aria-label="搜索"> <SearchIcon /> </Button>
  • 不要使用模糊的按钮文本: 文本应明确说明操作结果
{/* ❌ 错误:文本模糊 */} <Button>点击</Button> <Button>确定</Button> {/* ✅ 正确:文本明确 */} <Button>删除账户</Button> <Button>保存更改</Button>

常见问题

如何自定义按钮的宽度?

使用 className 添加宽度样式:

<Button className="w-full">全宽按钮</Button> <Button className="min-w-[200px]">最小宽度 200px</Button>

如何在按钮中使用自定义图标?

直接将图标作为 children 传入:

<Button> <CustomIcon className="mr-2" /> 按钮文本 </Button>

对于 link 变体,组件会自动在右侧添加箭头图标。如需自定义该图标的样式,使用 iconClassName 属性:

<Button variant="link" iconClassName="text-red-500"> 自定义箭头颜色 </Button>

Loading 状态时如何保持按钮宽度?

组件已自动处理,加载时会保持原始内容的宽度(通过 visibility: hidden 保留占位)。

如何实现按钮的防抖?

推荐在业务层实现防抖逻辑:

import { useMemo } from 'react' import { Button } from '@anker-in/headless-ui' import { debounce } from 'lodash' export default function DebouncedButton() { const handleClick = useMemo( () => debounce(() => { console.log('Debounced click') }, 500), [] ) return <Button onClick={handleClick}>点击我</Button> }

按钮在 Safari 中样式异常?

Button 组件已针对 Safari 进行优化。如遇到样式问题,请检查:

  1. 是否使用了自定义 className 覆盖了默认样式
  2. 父容器是否设置了影响布局的样式(如 display: grid
  3. 是否使用了浏览器默认样式重置(推荐使用 Tailwind 的 Preflight)

方式 1: 使用 as="a" 属性

<Button as="a" href="/products"> 查看产品 </Button>

方式 2: 包裹 Link 组件 (推荐用于 Next.js)

import Link from 'next/link' import { Button } from '@anker-in/headless-ui' <Link href="/products" passHref legacyBehavior> <Button as="a">查看产品</Button> </Link>

方式 3: 使用 className 包裹

import Link from 'next/link' <Link href="/products"> <span className="inline-block"> <Button>查看产品</Button> </span> </Link>

相关资源


更新日志

v2.0.0 (2025-01-17)

  • 🎉 完善组件文档,添加详细的使用指南和最佳实践
  • ✨ 新增 hoverEffect 属性支持滑动动画
  • ✨ 新增 as 属性替代已废弃的 asChild
  • 🔧 改进无障碍性支持(加载状态下的屏幕阅读器体验)
  • ⚠️ 破坏性变更: 废弃 ghost 变体,请使用 secondary 代替
  • ⚠️ 破坏性变更: 废弃 asChild 属性,请使用 as 代替

本文档有帮助吗?

在 GitHub 上反馈

Last updated on