Button 按钮

触发一次操作的按钮。变体决定视觉样式,色调决定语义。

<script setup lang="ts">
  import { Send } from '@lucide/vue'
  import { Button, Inline } from '@hina-ui/vue'
</script>

<template>
  <Inline>
    <Button variant="ghost" tone="neutral">取消</Button>
    <Button variant="outline" tone="neutral">存为草稿</Button>
    <Button>
      <template #icon>
        <Send />
      </template>
      发布
    </Button>
  </Inline>
</template>
vue

用法

import { Button } from '@hina-ui/vue'
ts

同一界面中仅设置一个主操作,其余操作采用较轻的变体,取消类操作采用中性色调。

<script setup lang="ts">
  import { Button, Inline } from '@hina-ui/vue'
</script>

<template>
  <Inline>
    <Button>保存</Button>
    <Button variant="outline" tone="neutral">取消</Button>
  </Inline>
</template>
vue

示例

变体

共五种视觉样式,从实心到纯文字,视觉重量依次减轻。link 只保留文字与下划线,没有底色与按下效果。

variant
tone
<template>
  <Button>按钮</Button>
</template>
vue

色调

accent 用于主操作,neutral 用于次要操作与取消,danger 用于不可撤销的操作。色调与变体相互独立。

tone
variant
<template>
  <Button>确认</Button>
</template>
vue

尺寸

共四种尺寸,高度与内边距随密度缩放。

size
variant
<template>
  <Button>按钮</Button>
</template>
vue

密度

密度在容器上声明,对容器内的所有控件生效。上排为默认密度,下排为紧凑密度。

<script setup lang="ts">
  import { Button, Inline, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack>
    <Inline>
      <Button size="sm">Small</Button>
      <Button size="md">Medium</Button>
      <Button size="lg">Large</Button>
    </Inline>
    <Inline data-density="compact">
      <Button size="sm">Small</Button>
      <Button size="md">Medium</Button>
      <Button size="lg">Large</Button>
    </Inline>
  </Stack>
</template>
vue

图标

#icon 用于放置前置图标,#trailing 用于放置后置图标。

<script setup lang="ts">
  import { ArrowRight, Plus, Settings } from '@lucide/vue'
  import { Button, IconButton, Inline } from '@hina-ui/vue'
</script>

<template>
  <Inline>
    <Button>
      <template #icon>
        <Plus />
      </template>
      新建
    </Button>
    <Button variant="outline" tone="neutral">
      下一步
      <template #trailing>
        <ArrowRight />
      </template>
    </Button>
    <IconButton label="设置" variant="outline">
      <Settings />
    </IconButton>
  </Inline>
</template>
vue

仅图标

仅包含图标时使用 IconButton。该组件要求提供 label,该值同时用作无障碍名称与悬停提示文字。

<script setup lang="ts">
  import { Bookmark, Check, Share2, Trash2 } from '@lucide/vue'
  import { IconButton, Inline } from '@hina-ui/vue'
</script>

<template>
  <Inline>
    <IconButton label="收藏">
      <Bookmark />
    </IconButton>
    <IconButton label="分享" variant="outline">
      <Share2 />
    </IconButton>
    <IconButton label="确认" variant="solid" tone="accent">
      <Check />
    </IconButton>
    <IconButton label="删除" variant="soft" tone="danger">
      <Trash2 />
    </IconButton>
  </Inline>
</template>
vue

按钮组

ButtonGroup 将多个按钮拼接为一个整体,相邻的圆角与边框会自动合并。设置 divider 可以在相邻按钮之间添加分隔线。

<script setup lang="ts">
  import { AlignCenter, AlignLeft, AlignRight } from '@lucide/vue'
  import { Button, ButtonGroup, IconButton, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack align="center">
    <ButtonGroup label="对齐方式">
      <IconButton label="左对齐" variant="outline" tone="neutral">
        <AlignLeft />
      </IconButton>
      <IconButton label="居中" variant="outline" tone="neutral">
        <AlignCenter />
      </IconButton>
      <IconButton label="右对齐" variant="outline" tone="neutral">
        <AlignRight />
      </IconButton>
    </ButtonGroup>

    <ButtonGroup label="视图" divider>
      <Button variant="soft" tone="neutral">列表</Button>
      <Button variant="soft" tone="neutral">网格</Button>
      <Button variant="soft" tone="neutral">时间线</Button>
    </ButtonGroup>
  </Stack>
</template>
vue

状态

设置 loading 显示加载指示器并阻止点击,设置 disabled 禁用按钮。

loading
disabled
variant
<template>
  <Button>保存</Button>
</template>
vue

加载指示器优先替换前置图标;无前置图标时替换后置图标;两者均无时居中覆盖于文字之上。此时文字仅设为透明而保留在原位,按钮宽度保持不变。

<script setup lang="ts">
  import { ArrowRight, Save } from '@lucide/vue'
  import { Button, Inline } from '@hina-ui/vue'
</script>

<template>
  <Inline>
    <Button loading>保存</Button>
    <Button loading variant="soft">
      <template #icon>
        <Save />
      </template>
      保存
    </Button>
    <Button loading variant="outline" tone="neutral">
      下一步
      <template #trailing>
        <ArrowRight />
      </template>
    </Button>
  </Inline>
</template>
vue

提交中

点击后进入加载状态,期间不再响应点击,文案随之切换。

<script setup lang="ts">
  import { ref } from 'vue'
  import { Upload } from '@lucide/vue'
  import { Button } from '@hina-ui/vue'

  const uploading = ref(false)

  function upload() {
    uploading.value = true
    setTimeout(() => (uploading.value = false), 2000)
  }
</script>

<template>
  <Button :loading="uploading" @click="upload">
    <template #icon>
      <Upload />
    </template>
    {{ uploading ? '上传中…' : '上传文件' }}
  </Button>
</template>
vue

第三方登录

设置 block 使按钮占满容器宽度,品牌图标放置于前置插槽。

<script setup lang="ts">
  import appleSvg from '@lobehub/icons-static-svg/icons/apple.svg?raw'
  import githubSvg from '@lobehub/icons-static-svg/icons/github.svg?raw'
  import googleSvg from '@lobehub/icons-static-svg/icons/google-color.svg?raw'
  import { Button, Stack } from '@hina-ui/vue'
  import RawIcon from '~/components/docs/RawIcon'

  const providers = [
    { svg: googleSvg, label: '使用 Google 登录' },
    { svg: githubSvg, label: '使用 GitHub 登录' },
    { svg: appleSvg, label: '使用 Apple 登录' },
  ]
</script>

<template>
  <Stack gap="sm" class="w-full max-w-xs">
    <Button
      v-for="provider in providers"
      :key="provider.label"
      variant="soft"
      tone="neutral"
      block
      pill
    >
      <template #icon>
        <RawIcon :svg="provider.svg" />
      </template>
      {{ provider.label }}
    </Button>
  </Stack>
</template>
vue

形状与宽度

设置 pill 使按钮呈胶囊形,设置 block 使其占满容器宽度。

pill
block
size
<template>
  <Button>继续</Button>
</template>
vue

按钮宽度受限时(例如加上 max-w-full),默认插槽里的 <Text truncate> 在按钮内以省略号截断。

自定义样式

Tailwind 类名

传入的 class 经 tailwind-merge 合并,同一属性以后声明者为准,无需 !important。

<script setup lang="ts">
  import { Button, Inline } from '@hina-ui/vue'
</script>

<template>
  <Inline>
    <Button class="rounded-none px-8">直角</Button>
    <Button variant="outline" tone="neutral" class="border-dashed">虚线边框</Button>
    <Button class="bg-purple-600 text-white hover:bg-purple-700">紫色</Button>
  </Inline>
</template>
vue

全局覆盖

按钮的颜色、圆角与焦点环均取自语义变量。在任意作用域中重新声明这些变量,该作用域内按钮的外观会随之改变。

.brand-purple {
  --hn-accent: oklch(0.55 0.22 300);
  --hn-accent-on: #ffffff;
  --hn-focus-ring: oklch(0.5 0.22 300);
}
css

样式参考

尺寸变量

变量默认紧凑
--hn-control-h-xs1.5rem1.25rem
--hn-control-h-sm1.75rem1.5rem
--hn-control-h-md2.25rem1.875rem
--hn-control-h-lg2.75rem2.25rem
--hn-control-px-xs0.5rem0.25rem
--hn-control-px-sm0.625rem0.375rem
--hn-control-px-md1rem0.5rem
--hn-control-px-lg1.25rem0.75rem
--hn-control-gap0.375rem0.25rem

紧凑密度适用于后台表格一类的密集界面。密度不随设备类型变化,如果触摸屏上需要更大的点击目标,应当显式使用默认密度。

状态属性

渲染为原生 button 时直接使用原生的 disabled。渲染为其他元素时不具备原生的禁用语义,改由以下属性表示状态。

属性含义
data-loading加载中
aria-busy加载中,供屏幕阅读器播报
data-disabled不可用
aria-disabled不可用,供屏幕阅读器播报
tabindex="-1"不可用,键盘不会聚焦到该按钮

加载中的按钮同样视为不可用。

悬停与按下

  • 焦点环仅在键盘聚焦时出现,鼠标点击不留下轮廓。
  • 悬停与按下时会叠加一层半透明色,而不是替换底色;触摸设备上不会残留悬停状态。
  • 波纹自按下的位置扩散,此时半透明层的按下效果不再叠加。link 变体没有波纹。

无障碍

  • 仅包含图标的按钮必须提供无障碍名称,未提供时开发环境将输出告警。建议直接使用 IconButton。
  • 加载中的按钮带有 aria-busy,屏幕阅读器会播报忙碌状态。

API

Props

属性
类型
默认值
说明
as
string | Component
'button'
渲染的元素或组件
asChild
boolean
false
不渲染自身,合并至唯一的子元素
variant
'solid' | 'soft' | 'outline' | 'ghost' | 'link'
'solid'
视觉样式
tone
'accent' | 'neutral' | 'danger'
'accent'
语义色调
size
'xs' | 'sm' | 'md' | 'lg'
'md'
尺寸
type
'button' | 'submit' | 'reset'
'button'
原生 button 类型
iconOnly
boolean
false
是否为正方形且仅包含图标
block
boolean
false
是否占满容器宽度
pill
boolean
false
是否呈胶囊形
loading
boolean
false
是否处于加载状态
disabled
boolean
false
是否禁用
ripple
boolean
true
是否启用按下波纹,link 变体不生效
class
string
—
追加至根元素的类名

Slots

插槽
说明
default
按钮内容,多个子元素之间自动隔开
icon
前置图标,加载时被指示器替换
trailing
后置图标,仅在无前置图标时被替换