<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>
用法
import { Button } from '@hina-ui/vue'
同一界面中仅设置一个主操作,其余操作采用较轻的变体,取消类操作采用中性色调。
<script setup lang="ts">
import { Button, Inline } from '@hina-ui/vue'
</script>
<template>
<Inline>
<Button>保存</Button>
<Button variant="outline" tone="neutral">取消</Button>
</Inline>
</template>
示例
变体
共五种视觉样式,从实心到纯文字,视觉重量依次减轻。link 只保留文字与下划线,没有底色与按下效果。
<template>
<Button>按钮</Button>
</template>色调
accent 用于主操作,neutral 用于次要操作与取消,danger 用于不可撤销的操作。色调与变体相互独立。
<template>
<Button>确认</Button>
</template>尺寸
共四种尺寸,高度与内边距随密度缩放。
<template>
<Button>按钮</Button>
</template>密度
密度在容器上声明,对容器内的所有控件生效。上排为默认密度,下排为紧凑密度。
<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>
图标
#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>
仅图标
仅包含图标时使用 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>
按钮组
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>
状态
设置 loading 显示加载指示器并阻止点击,设置 disabled 禁用按钮。
<template>
<Button>保存</Button>
</template>加载指示器优先替换前置图标;无前置图标时替换后置图标;两者均无时居中覆盖于文字之上。此时文字仅设为透明而保留在原位,按钮宽度保持不变。
<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>
提交中
点击后进入加载状态,期间不再响应点击,文案随之切换。
<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>
第三方登录
设置 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>
形状与宽度
设置 pill 使按钮呈胶囊形,设置 block 使其占满容器宽度。
<template>
<Button>继续</Button>
</template>按钮宽度受限时(例如加上 max-w-full),默认插槽里的 <Text truncate> 在按钮内以省略号截断。
作为链接
通过 as 将按钮渲染为 a 元素或者 NuxtLink,外观与交互保持一致。
<script setup lang="ts">
import { ArrowUpRight } from '@lucide/vue'
import { Button, Inline } from '@hina-ui/vue'
</script>
<template>
<Inline>
<Button
as="a"
href="https://www.hikarinagi.com"
target="_blank"
rel="noreferrer"
variant="outline"
>
访问 Hikarinagi
<template #trailing>
<ArrowUpRight />
</template>
</Button>
<Button
as="a"
href="https://www.hikarinagi.com"
target="_blank"
rel="noreferrer"
variant="link"
>
了解更多
</Button>
</Inline>
</template>
仅需链接外观而不需要按钮语义时,应使用 Link 组件,而非 variant="link"。
如果目标组件需要自行渲染根元素,改用 asChild:按钮不渲染自身,而是将类名与行为合并至唯一的子元素。
此模式下,图标、波纹和加载指示等内容由子元素负责。loading 仍会设置忙碌状态并拦截点击。
<template>
<Button as-child>
<NuxtLink to="/guide/installation">开始使用</NuxtLink>
</Button>
</template>
自定义样式
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>
全局覆盖
按钮的颜色、圆角与焦点环均取自语义变量。在任意作用域中重新声明这些变量,该作用域内按钮的外观会随之改变。
.brand-purple {
--hn-accent: oklch(0.55 0.22 300);
--hn-accent-on: #ffffff;
--hn-focus-ring: oklch(0.5 0.22 300);
}
样式参考
尺寸变量
| 变量 | 默认 | 紧凑 |
|---|---|---|
--hn-control-h-xs | 1.5rem | 1.25rem |
--hn-control-h-sm | 1.75rem | 1.5rem |
--hn-control-h-md | 2.25rem | 1.875rem |
--hn-control-h-lg | 2.75rem | 2.25rem |
--hn-control-px-xs | 0.5rem | 0.25rem |
--hn-control-px-sm | 0.625rem | 0.375rem |
--hn-control-px-md | 1rem | 0.5rem |
--hn-control-px-lg | 1.25rem | 0.75rem |
--hn-control-gap | 0.375rem | 0.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 | 后置图标,仅在无前置图标时被替换 |