# Button

UI version: 0.1.9
Source commit: b6578306f58bf0918d0f4cb8519cf4cf1a900b15
Page: https://apollocmh.github.io/apollo-design-ui/components/button


按钮触发操作，或通过原生链接导航。

## 何时使用

用于提交表单、确认操作或触发当前页面中的动作。需要跳转时使用 `href`，由原生链接承载导航。

## 代码演示

保留全部 20 个原示例：`basic`, `block`, `chinese-chars-loading`, `chinese-space`, `color-variant`, `component-token`, `custom-disabled-bg`, `danger`, `disabled`, `ghost`, `href`, `icon`, `icon-placement`, `linear-gradient`, `loading`, `multiple`, `semantic`, `shape`, `size`, `style-class`.

## API

### Props

| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| type | 视觉类型糖，显式 color/variant 可分别覆盖 | `ButtonType` | 'default' |
| color | 独立生效的语义色或预设色 | `ButtonColorType` | — |
| variant | 独立生效的视觉变体 | `ButtonVariantType` | — |
| icon | 图标数据或 Vue 组件；#icon 优先 | `ButtonIcon` | — |
| iconPosition | 已有废弃别名，使用 iconPlacement | `ButtonIconPlacement` | — |
| iconPlacement | 图标位置，覆盖已有别名 | `ButtonIconPlacement` | 'start' |
| shape | 形状；square 为方角 | `ButtonShape` | 'default' |
| size | 尺寸，依次取局部、Compact、Provider | `ButtonSize` | — |
| disabled | 禁用；显式 false 覆盖父级 true | `boolean` | — |
| loading | 请求立即阻止点击，delay 只推迟指示器 | `ButtonLoading` | false |
| prefixCls | 根类名前缀 | `string` | apollo-btn |
| ghost | 透明背景；solid 映射为 outlined | `boolean` | false |
| danger | 危险颜色糖，低于显式 color | `boolean` | false |
| block | 撑满容器 | `boolean` | false |
| href | 定义时渲染 a，空字符串也是有效链接值 | `string` | — |
| htmlType | button 根的原生 type | `ButtonHTMLType` | 'button' |
| autoInsertSpace | 两个中文字的视觉字距，保留连续原文 | `boolean` | true |
| classNames | root/icon/content 对象式类名 | `ButtonSemanticClassNames` | — |
| styles | root/icon/content 对象式样式 | `ButtonSemanticStyles` | — |

`ButtonType`: default / primary / dashed / link / text. `ButtonShape`: default / circle / round / square.
`ButtonSize`: small / medium / middle / large. `ButtonVariantType`: outlined / dashed / solid / filled / text / link.
`ButtonColorType`: default / primary / danger / blue / purple / cyan / green / magenta / pink / red / orange / yellow / volcano / geekblue / lime / gold.
`ButtonHTMLType`: button / submit / reset. `ButtonIconPlacement`: start / end.
`ButtonIcon = VNodeChild | Component`; `ButtonLoading = boolean | { delay?: number; icon?: ButtonIcon }`.

### 当前配置与原生属性

color、variant 各自先取显式值，再取 type/danger 糖，最后取当前 Provider；没有颜色时 solid 用 primary，其他变体用 default。显式 color 优先于 danger。ghost 把 solid 映射为 outlined。Provider 替换、删除、恢复均使用当前配置。

Vue class、字符串/对象/数组 style 和其他原生 attrs 落在真实 button/anchor 根；原生 style 优先于语义根样式和源 Token。表单属性、原生事件正常消费。启用链接尊重调用者 tabindex；禁用时移除 href 并强制 tabindex=-1。自定义前缀需配套静态 CSS，例如 genButtonStyle('my-app') 对应 .my-app-btn。

### 插槽

| 名称 | 内容 |
|---|---|
| default | 完整 Vue 内容；注释和空 Fragment 不产生空宿主 |
| icon | 完整 Vue 图标内容，优先于 icon 数据；有意空槽隐藏回退 |

组件拥有的 .apollo-btn-content 接收 content 类和样式。业务子 VNode、文字、状态和 ref 保持原样；中文字距由 CSS 实现，DOM 和可访问名保留连续原文。语义样式仅接受对象，不包含函数式 API。

### Emits 与 ref

click 发出一次原生 MouseEvent。disabled 或加载请求阻止激活、默认提交和事件传播，延迟显示期间也不触发 click。加载保留焦点，通过 aria-busy/aria-disabled 表达状态。有限正 delay 只延迟指示器；取消或卸载清理，单独替换图标不重计时，SSR 不创建计时器。内置加载图形为装饰；仅图标按钮应通过 aria-label 提供操作名称。

唯一 expose 是 nativeElement: HTMLButtonElement | HTMLAnchorElement | null，挂载前及卸载后为 null。没有 v-model 或其他 focus/blur 服务。

### 组件 Token 与导出

下列 45 个显式字段及 13 个已有预设阴影字段均消费当前 theme.components.Button。原生根变量（如 --apollo-btn-content-font-size）优先。数字长度转 px，字重/行高为无单位；有限非负尺寸保留 0，无效数字尺寸回退。移除源配置恢复继承/静态默认值。

| Token | 职责 |
|---|---|
| fontWeight | 字重 |
| iconGap | 图标与内容间距 |
| defaultShadow | 阴影 |
| primaryShadow | 阴影 |
| dangerShadow | 阴影 |
| primaryColor | 主色实心按钮三态文字色，默认 colorTextLightSolid（白色） |
| defaultColor | 对应颜色及状态的文字、背景或边框 |
| defaultBg | 对应颜色及状态的文字、背景或边框 |
| defaultBorderColor | 对应颜色及状态的文字、背景或边框 |
| dangerColor | 危险实心按钮三态文字色，默认 colorTextLightSolid（白色） |
| defaultHoverBg | 对应颜色及状态的文字、背景或边框 |
| defaultHoverColor | 对应颜色及状态的文字、背景或边框 |
| defaultHoverBorderColor | 对应颜色及状态的文字、背景或边框 |
| defaultActiveBg | 对应颜色及状态的文字、背景或边框 |
| defaultActiveColor | 对应颜色及状态的文字、背景或边框 |
| defaultActiveBorderColor | 对应颜色及状态的文字、背景或边框 |
| borderColorDisabled | 对应颜色及状态的文字、背景或边框 |
| defaultBorderColorDisabled | 对应颜色及状态的文字、背景或边框 |
| groupBorderColor | 组及 Compact 接缝 |
| defaultGhostColor | 对应颜色及状态的文字、背景或边框 |
| ghostBg | 对应颜色及状态的文字、背景或边框 |
| defaultGhostBorderColor | 对应颜色及状态的文字、背景或边框 |
| solidTextColor | 默认及预设实心按钮的显式文字色；不覆盖 primaryColor/dangerColor |
| textTextColor | 对应颜色及状态的文字、背景或边框 |
| textTextHoverColor | 对应颜色及状态的文字、背景或边框 |
| textTextActiveColor | 对应颜色及状态的文字、背景或边框 |
| paddingInline | 对应尺寸内边距 |
| paddingInlineLG | 对应尺寸内边距 |
| paddingInlineSM | 对应尺寸内边距 |
| paddingBlock | 对应尺寸内边距 |
| paddingBlockLG | 对应尺寸内边距 |
| paddingBlockSM | 对应尺寸内边距 |
| onlyIconSize | 仅图标尺寸 |
| onlyIconSizeLG | 仅图标尺寸 |
| onlyIconSizeSM | 仅图标尺寸 |
| linkHoverBg | 对应颜色及状态的文字、背景或边框 |
| textHoverBg | 对应颜色及状态的文字、背景或边框 |
| contentFontSize | 对应尺寸字号 |
| contentFontSizeLG | 对应尺寸字号 |
| contentFontSizeSM | 对应尺寸字号 |
| contentLineHeight | 对应尺寸行高 |
| contentLineHeightLG | 对应尺寸行高 |
| contentLineHeightSM | 对应尺寸行高 |
| defaultBgDisabled | 对应颜色及状态的文字、背景或边框 |
| dashedBgDisabled | 对应颜色及状态的文字、背景或边框 |

预设阴影：`blueShadowColor`, `purpleShadowColor`, `cyanShadowColor`, `greenShadowColor`, `magentaShadowColor`, `pinkShadowColor`, `redShadowColor`, `orangeShadowColor`, `yellowShadowColor`, `volcanoShadowColor`, `geekblueShadowColor`, `limeShadowColor`, `goldShadowColor`.

```vue
<ConfigProvider :theme="{components:{Button:{contentFontSize:18,paddingInline:24}}}">
  <Button type="primary">Save</Button>
</ConfigProvider>
```

主色与危险实心按钮默认使用 colorTextLightSolid，普通、悬停、按下均保持浅色文字，不使用自动黑白前景替换。支持相对颜色的浏览器会限制这些实心背景的过高亮度，保证默认白字可读性，保留全局 Alias 原值。默认及预设实心按钮保留自动前景；彩色文字保留原生 CSS 明度修正。显式前景 Token 和原生样式优先；自定义文字、透明及渐变表面须按实际背景配对。不支持的色值函数保留 Alias 回退，实际验证范围见审计记录。

Button、genButtonStyle、prepareButtonComponentToken 从 @apollo-design/ui 和 @apollo-design/ui/button 导出。类型含 ButtonProps、ButtonRef、ButtonSlots、ButtonSlot、ButtonConfig、上述枚举/语义类型、ButtonComponentToken、ButtonTokenInput。旧文档中的 button/style/token 不是包导出路径。

```ts
import '@apollo-design/theme/tokens.css';
import '@apollo-design/ui/button/style.css';
// Or import '@apollo-design/ui/style.css';
```

[契约迁移](https://github.com/apollocmh/apollo-design-ui/blob/b6578306f58bf0918d0f4cb8519cf4cf1a900b15/docs/migrations/button-contracts.md)。


## 按钮类型 (basic)

`type` 是 v5 及更早版本的「糖」写法，v6 里会被解析成 `color` + `variant`：

| type | color | variant |
|---|---|---|
| `primary` | `primary` | `solid` |
| `default` | `default` | `outlined` |
| `dashed` | `default` | `dashed` |
| `text` | `default` | `text` |
| `link` | `link` | `link` |

最后一颗按钮演示「两个中文字符自动插空格」：`autoInsertSpace` 默认为 `true`。

### basic.vue

```vue
<script setup lang="ts">
import { Button, Space } from '@apollo-design/ui';
</script>

<template>
  <Space wrap>
    <Button type="primary">Primary</Button>
    <Button>Default</Button>
    <Button type="dashed">Dashed</Button>
    <Button type="text">Text</Button>
    <Button type="link">Link</Button>
    <!-- 两个中文字符会自动视觉字距（-two-chinese-chars） -->
    <Button type="primary">确定</Button>
  </Space>
</template>
```

## 按钮尺寸 (size)

`size` 的默认档是 `medium`（旧写法 `middle` 同样合法），此时**不**输出 `-lg` / `-sm` 类名。

### size.vue

```vue
<script setup lang="ts">
import { Button, Space } from '@apollo-design/ui';
</script>

<template>
  <Space wrap>
    <Button type="primary" size="large">Large</Button>
    <Button type="primary" size="medium">Medium</Button>
    <Button type="primary">Default</Button>
    <Button type="primary" size="small">Small</Button>
  </Space>
</template>
```

## 加载中 (loading)

`loading` 有两种形态：

- 布尔：`true` 立刻进入加载态。
- 对象：`{ delay, icon }`。`delay > 0` 时**延迟到点才置为加载态**，用于防止连点；
  变回 `false` **只能靠 `loading` prop 自己变回**，不会自动复位。

加载中时点击被拦截（`preventDefault` 且不 emit `click`）。

### loading.vue

```vue
<script setup lang="ts">
import { Button, Space } from '@apollo-design/ui';
import { onUnmounted, ref } from 'vue';

const loading = ref(false);
const delayLoading = ref(false);
const count = ref(0);
let immediateTimer: ReturnType<typeof setTimeout> | undefined;
let delayedTimer: ReturnType<typeof setTimeout> | undefined;
function enter(delayed: boolean) {
  const current = delayed ? delayLoading : loading;
  if (current.value) return;
  current.value = true;
  count.value++;
  const finish = () => {
    current.value = false;
  };
  if (delayed) delayedTimer = setTimeout(finish, 3000);
  else immediateTimer = setTimeout(finish, 3000);
}
onUnmounted(() => {
  clearTimeout(immediateTimer);
  clearTimeout(delayedTimer);
});
</script>
<template>
  <Space orientation="vertical">
    <Space wrap>
      <Button type="primary" loading>Loading</Button>
      <Button type="primary" :loading="delayLoading ? { delay: 1000 } : false" @click="enter(true)">
        Delay 1s
      </Button>
      <Button type="primary" :loading="loading" @click="enter(false)">Click me</Button>
    </Space>
    <p aria-live="polite" style="margin:0;color:var(--apollo-color-text-secondary)">
      已触发 {{ count }} 次；加载请求立即阻止重复点击，1 秒只延迟指示器显示，3 秒后恢复。
    </p>
  </Space>
</template>
```

## 不可用状态 (disabled)

⚠️ 两个分支的 disabled 表达**不对称**，这是上游真实行为（不是笔误）：

| 分支 | 表现 |
|---|---|
| `<button>`（无 `href`） | 原生 `disabled` 属性 |
| `<a>`（有 `href`） | 移除 `href` + `tabindex="-1"` + `aria-disabled` |

### disabled.vue

```vue
<script setup lang="ts">
import { Button, Space } from '@apollo-design/ui';
</script>

<template>
  <Space wrap>
    <Button type="primary">Primary</Button>
    <Button type="primary" disabled>Primary(disabled)</Button>
    <Button type="text">Text</Button>
    <Button type="text" disabled>Text(disabled)</Button>
    <Button type="link">Link</Button>
    <Button type="link" disabled>Link(disabled)</Button>
    <Button type="primary" href="https://example.com">Href</Button>
    <Button type="primary" href="https://example.com" disabled>Href(disabled)</Button>
  </Space>
</template>
```

## 危险按钮 (danger)

`danger` 会把解析出的 `color` 换成 `danger`。

⚠️ 类名上有**两处不一致**（上游真实行为）：

- `-dangerous` 用的是**原始 `danger` prop**
- `-color-dangerous` 里 danger 被改写成 `dangerous`（不是 `-color-danger`）

### danger.vue

```vue
<script setup lang="ts">
import { Button, Space } from '@apollo-design/ui';
</script>

<template>
  <Space wrap>
    <Button type="primary" danger>Primary</Button>
    <Button danger>Default</Button>
    <Button type="dashed" danger>Dashed</Button>
    <Button type="text" danger>Text</Button>
    <Button type="link" danger>Link</Button>
  </Space>
</template>
```

## 幽灵按钮 (ghost)

幽灵按钮把背景变透明，常用在有色背景上。

⚠️ `ghost` 对 `solid` 变体的处理是**退化成 `outlined`**（不是加个类了事）。

⚠️ 属性顺序有讲究：`ghost` 必须写在 `danger` **之后**才不会被 `danger` 的样式压过。

### ghost.vue

```vue
<script setup lang="ts">
import { Button, ConfigProvider, Space } from '@apollo-design/ui';
</script>
<template>
  <ConfigProvider :theme="{components:{Button:{defaultGhostColor:'var(--apollo-color-text)',defaultGhostBorderColor:'var(--apollo-color-border)'}}}">
    <div style="background:var(--apollo-color-fill-tertiary);padding:16px">
      <Space wrap>
        <Button type="primary" ghost>Primary</Button>
        <Button ghost>Default</Button>
        <Button type="dashed" ghost>Dashed</Button>
        <Button type="primary" danger ghost>Danger</Button>
      </Space>
    </div>
  </ConfigProvider>
</template>
```

## 块级按钮 (block)

`block` 让按钮撑满父容器宽度（`-block` 类名 → `width: 100%`）。

### block.vue

```vue
<script setup lang="ts">
import { Button, Space } from '@apollo-design/ui';
</script>

<template>
  <Space orientation="vertical" style="width: 100%">
    <Button type="primary" block>Primary</Button>
    <Button block>Default</Button>
    <Button type="dashed" block>Dashed</Button>
  </Space>
</template>
```

## 图标按钮 (icon)

`icon` 在 v6 里是**节点**，不是 v4 的字符串名（传字符串且长度 > 2 会输出告警）。
也可以传**组件** —— Vue 没有 React 的「元素」形态，对应物就是组件本身（差异 D42）。

- `icon` prop 与 `icon` 插槽都能传图标，**prop 优先**（`props.icon ?? slots.icon()`）。
- `iconPlacement="end"` 把图标放到文字后面（靠 `-icon-end` 类名用 CSS 翻转，
  **不是**调换 DOM 顺序）。
- 没有默认插槽且有图标时加 `-icon-only` 类名。
- `loading` 时图标换成加载图标（`loading.icon` > ConfigProvider 的 `loadingIcon` > 内置 `LoadingOutlined`）。
  容器上额外带 `-loading-icon` 类名 —— ⚠️ **只在前两支都没提供自定义图标时才带**；
  一旦有自定义加载图标（prop 或 ConfigProvider），该类名**不出现**。
- 插槽仍只接受 `() => VNodeChild`；组件要走 prop。

### icon.vue

```vue
<script setup lang="ts">
import { SearchOutlined } from '@apollo-design/icons';
import { Button, Space } from '@apollo-design/ui';
</script>

<template>
  <Space wrap>
    <Button type="primary" :icon="SearchOutlined">Search</Button>
    <Button :icon="SearchOutlined" icon-placement="end">Search</Button>
    <Button type="primary" :icon="SearchOutlined" aria-label="Search" />
    <!-- 用插槽传图标 -->
    <Button type="primary">
      <template #icon><SearchOutlined /></template>
      Slot icon
    </Button>
  </Space>
</template>
```

## 按钮形状 (shape)

`shape` 取 `default` 与 `square` 时**不**产出 `-{shape}` 类名，其余（`circle` / `round`）产出。

### shape.vue

```vue
<script setup lang="ts">
import { SearchOutlined } from '@apollo-design/icons';
import { Button, Space } from '@apollo-design/ui';
</script>

<template>
  <Space wrap>
    <Button type="primary" shape="circle" :icon="SearchOutlined" aria-label="Search" />
    <Button type="primary" shape="round">Round</Button>
    <Button type="primary" shape="square">Square</Button>
    <Button shape="circle" :icon="SearchOutlined" aria-label="Search" />
    <Button shape="round">Round</Button>
  </Space>
</template>
```

## 颜色与变体 (color-variant)

`color` + `variant` 是 v6 的新写法，**同时给出时优先级最高**（压过 `type` / `danger`）。

- `color`：16 个预设语义色（含 `default` / `primary` / `danger` + 13 个色板色）
- `variant`：`outlined` / `dashed` / `solid` / `filled` / `text` / `link`

⚠️ `filled` 在 `ButtonTypeMap` 里**没有**映射 ⇒ 只能由 `variant` 显式传入。

### color-variant.vue

```vue
<script setup lang="ts">
import { Button, Space } from '@apollo-design/ui';
</script>

<template>
  <Space wrap>
    <Button color="blue" variant="solid">Blue solid</Button>
    <Button color="purple" variant="outlined">Purple outlined</Button>
    <Button color="cyan" variant="filled">Cyan filled</Button>
    <Button color="green" variant="dashed">Green dashed</Button>
    <Button color="volcano" variant="text">Volcano text</Button>
    <Button color="gold" variant="link">Gold link</Button>
  </Space>
</template>
```

## 链接按钮 (href)

传了 `href` 就渲染 `<a>` 而不是 `<button>`。此时 `htmlType` 无效、没有原生 `disabled`，
禁用靠「移除 `href` + `tabindex="-1"` + `aria-disabled`」。

### href.vue

```vue
<script setup lang="ts">
import { Button, Space } from '@apollo-design/ui';
</script>

<template>
  <Space wrap>
    <Button type="primary" href="https://example.com">Href</Button>
    <Button href="https://example.com" target="_blank">Href(target=_blank)</Button>
    <Button type="link" href="https://example.com">Link</Button>
  </Space>
</template>
```

## 语义化 classNames / styles (semantic)

`classNames` / `styles` 各有三个槽位：`root` / `icon` / `content`。

合并优先级（低 → 高）：



⚠️ 与 antd 的差异：本仓库**不支持函数式变体**（`classNames` / `styles` 只接受对象），
依据 `empty-semantic-fn` 开放决策的建议 B，与已完成的 divider / empty / space / spin 一致。

### semantic.vue

```vue
<script setup lang="ts">
import { SearchOutlined } from '@apollo-design/icons';
import { Button } from '@apollo-design/ui';
</script>

<template>
  <Button
    type="primary"
    :icon="SearchOutlined"
    :class-names="{ root: 'demo-btn-root', icon: 'demo-btn-icon', content: 'demo-btn-content' }"
    :styles="{ root: { borderRadius: '16px' }, content: { letterSpacing: '2px' } }"
  >
    Semantic
  </Button>
</template>
```

## 组件 Token (component-token)

通过当前 `ConfigProvider.theme.components.Button` 修改已有 Token，并保留 Alias 主色/圆角定制。支持动态配置、移除恢复、原生根变量覆盖；草稿和点击计数用于观察内容与状态是否保留。

主按钮默认使用浅色文字；“自定义主按钮文字”演示 `primaryColor` 的显式覆盖，关闭后恢复默认白字。浅色实心背景会限制过高亮度以保持文字可读性；自定义前景、渐变和透明背景需自行配对。

### component-token.vue

```vue
<script setup lang="ts">
import { Button, Checkbox, ConfigProvider, Input, Space } from '@apollo-design/ui';
import { computed, ref } from 'vue';

const clicks = ref(0);
const enabled = ref(true);
const enlarged = ref(false);
const native = ref(false);
const customForeground = ref(false);
const theme = computed(() =>
  enabled.value
    ? {
        token: { colorPrimary: '#006b45', borderRadius: 16 },
        components: {
          Button: {
            ...(customForeground.value ? { primaryColor: '#fff6e6' } : {}),
            fontWeight: 600,
            iconGap: 12,
            paddingInline: 24,
            contentFontSize: enlarged.value ? 18 : 14,
            defaultBg: '#e8f5ee',
            defaultColor: '#123b2c',
            defaultBorderColor: '#33775b',
          },
        },
      }
    : undefined,
);
</script>
<template>
  <div class="demo-button-token">
    <fieldset>
      <legend>当前配置</legend>
      <Checkbox v-model:checked="enabled">启用组件 Token</Checkbox>
      <Checkbox v-model:checked="enlarged">放大字号</Checkbox>
      <Checkbox v-model:checked="native">原生变量优先</Checkbox>
      <Checkbox v-model:checked="customForeground">自定义主按钮文字</Checkbox>
    </fieldset>
    <ConfigProvider :theme="theme">
      <Space wrap>
        <Button type="primary" :style="native ? {'--apollo-btn-content-font-size':'22px'} : undefined" @click="clicks++">Primary</Button>
        <Button>Default</Button>
        <Button type="primary" danger>Danger</Button>
      </Space>
    </ConfigProvider>
    <label>保留草稿 <Input aria-label="Button token draft" placeholder="切换配置时保留输入和焦点" style="max-width: 280px" /></label>
    <p aria-live="polite" class="demo-button-token__hint">
      点击 Primary 次数：{{ clicks }}；切换或移除当前组件配置，原生根变量仍有最高优先级。
    </p>
  </div>
</template>
<style>
.demo-button-token {display:flex;flex-direction:column;gap:12px;font-family:var(--apollo-font-family);}
.demo-button-token fieldset {display:flex;flex-wrap:wrap;gap:12px;}
.demo-button-token__hint {margin:0;color:var(--apollo-color-text-secondary);font-size:12px;}
</style>
```

## 中文 loading (chinese-chars-loading)

两个中文字按钮在 `loading` 态下仍会保持自动插入的间距，可点击「提交」观察加载到完成的切换。

### chinese-chars-loading.vue

```vue
<script setup lang="ts">
// 两个中文字 + loading：视觉字距与加载态共存
import { Button, Space } from '@apollo-design/ui';
import { onUnmounted, ref } from 'vue';

const loading = ref(false);
const status = ref('未提交');
let timer: ReturnType<typeof setTimeout> | undefined;
onUnmounted(() => clearTimeout(timer));

function submit(): void {
  if (loading.value) return;
  loading.value = true;
  status.value = '提交中…';
  timer = setTimeout(() => {
    loading.value = false;
    status.value = '提交完成';
  }, 1500);
}
</script>

<template>
  <Space orientation="vertical" :size="12" style="font-family: sans-serif">
    <Space wrap>
      <Button type="primary" loading>确定</Button>
      <Button loading>取消</Button>
      <Button type="primary" :loading="loading" @click="submit">提交</Button>
    </Space>
    <div aria-live="polite" style="font-size: 12px; color: var(--apollo-color-text-secondary)">
      状态：{{ status }}（loading 图标与两个中文字之间仍保持自动间距）
    </div>
  </Space>
</template>
```

## 中文间距 (chinese-space)

`autoInsertSpace` 控制两个中文字之间是否自动插入空格，默认开启；对比点击可观察间距差异。

### chinese-space.vue

```vue
<script setup lang="ts">
// autoInsertSpace：两个中文字之间是否自动视觉字距（默认 true）
import { Button, Space } from '@apollo-design/ui';
import { ref } from 'vue';

const last = ref('');
</script>

<template>
  <Space orientation="vertical" :size="12" style="font-family: sans-serif">
    <Space wrap>
      <Button @click="last = '确定（默认视觉字距）'">确定</Button>
      <Button @click="last = '取消（默认视觉字距）'">取消</Button>
      <Button :auto-insert-space="false" @click="last = '确定（autoInsertSpace=false）'">确定</Button>
      <Button :auto-insert-space="false" @click="last = '取消（autoInsertSpace=false）'">取消</Button>
    </Space>
    <div style="font-size: 12px; color: var(--apollo-color-text-secondary)">最近点击：{{ last || '-' }}</div>
  </Space>
</template>
```

## 图标位置 (icon-placement)

`iconPlacement` 取 `'start'` 或 `'end'`，控制 `icon` 相对文字的位置；仅图标按钮可配合 `shape="circle"`。

### icon-placement.vue

```vue
<script setup lang="ts">
// iconPlacement：图标在文字之前或之后
import { SearchOutlined } from '@apollo-design/icons';
import { Button, Space } from '@apollo-design/ui';
import { ref } from 'vue';

const last = ref('');
</script>

<template>
  <div style="font-family: sans-serif">
    <Space wrap>
      <Button
        type="primary"
        :icon="SearchOutlined"
        icon-placement="start"
        @click="last = 'start'"
      >
        搜索
      </Button>
      <Button type="primary" :icon="SearchOutlined" icon-placement="end" @click="last = 'end'">搜索</Button>
      <Button :icon="SearchOutlined" icon-placement="end" @click="last = 'default-end'">设置</Button>
      <Button
        type="primary"
        :icon="SearchOutlined"
        shape="circle"
        aria-label="搜索"
        @click="last = 'circle'"
      />
    </Space>
    <div style="margin-top: 12px; font-size: 12px; color: var(--apollo-color-text-secondary)">
      最近点击的图标位置：{{ last || '-' }}
    </div>
  </div>
</template>
```

## 自定义禁用背景 (custom-disabled-bg)

通过 `ConfigProvider` 的 `theme.token` 覆盖 `colorBgContainerDisabled`、`colorBorderDisabled` 与 `colorTextDisabled`，统一改变禁用按钮配色。

### custom-disabled-bg.vue

```vue
<script setup lang="ts">
// 自定义禁用态背景 / 边框 / 文字色（ConfigProvider theme.token）
import { Button, ConfigProvider, Space } from '@apollo-design/ui';
import { ref } from 'vue';

const clicks = ref(0);
</script>

<template>
  <div style="font-family: sans-serif">
    <ConfigProvider
      :theme="{
        token: {
          colorBgContainerDisabled: '#e6f4ff',
          colorBorderDisabled: '#91caff',
          colorTextDisabled: '#1677ff',
        },
      }"
    >
      <Space wrap>
        <Button disabled>Default(disabled)</Button>
        <Button type="primary" disabled>Primary(disabled)</Button>
        <Button danger disabled>Danger(disabled)</Button>
        <Button type="primary" @click="clicks++">可点击 {{ clicks }}</Button>
      </Space>
    </ConfigProvider>
    <div style="margin-top: 12px; font-size: 12px; color: var(--apollo-color-text-secondary)">
      上方禁用按钮的背景 / 边框 / 文字来自 ConfigProvider 的 theme.token 覆盖
    </div>
  </div>
</template>
```

## 线性渐变 (linear-gradient)

用自定义类覆盖主按钮的 `background-image`，实现线性渐变；悬停与按下状态使用同色系更亮的渐变。禁用按钮保留组件自身的禁用样式。

### linear-gradient.vue

```vue
<script setup lang="ts">
// 线性渐变：用类名覆盖主按钮背景
import { Button, Space } from '@apollo-design/ui';
</script>

<template>
  <div class="demo-button-gradient-wrap">
    <Space wrap>
      <Button class="demo-button-gradient" type="primary" size="large">Linear Gradient</Button>
      <Button class="demo-button-gradient demo-button-gradient--warm" type="primary">
        Warm Gradient
      </Button>
      <Button class="demo-button-gradient" type="primary" disabled>Disabled Gradient</Button>
    </Space>
    <p class="demo-button-gradient__hint">
      渐变只覆盖背景图像，悬停与按下状态保持同色系，并为白色文字配对足够深的渐变端点。
    </p>
  </div>
</template>

<style>
.demo-button-gradient.apollo-btn {
  color: #fff;
  border: 0;
  background-image: linear-gradient(135deg, #0958d9 0%, #531dab 100%);
  box-shadow: 0 6px 16px rgb(22 119 255 / 28%);
}

.demo-button-gradient.apollo-btn:not(:disabled):hover {
  color: #fff;
  background-image: linear-gradient(135deg, #0958d9 0%, #722ed1 100%);
  box-shadow: 0 8px 20px rgb(114 46 209 / 30%);
}

.demo-button-gradient.apollo-btn:not(:disabled):active {
  color: #fff;
  background-image: linear-gradient(135deg, #0958d9 0%, #531dab 100%);
}

.demo-button-gradient--warm.apollo-btn {
  background-image: linear-gradient(135deg, #b33a00 0%, #a8071a 100%);
  box-shadow: 0 6px 16px rgb(245 34 45 / 26%);
}

.demo-button-gradient--warm.apollo-btn:not(:disabled):hover {
  background-image: linear-gradient(135deg, #c43d00 0%, #a8071a 100%);
}

.demo-button-gradient.apollo-btn:disabled {
  background-image: none;
  color: var(--apollo-color-text-disabled);
}

.demo-button-gradient-wrap {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.demo-button-gradient__hint {
  margin: 0;
  color: var(--apollo-color-text-secondary);
  font-size: 12px;
}
</style>
```

## 多个按钮 (multiple)

把一组相关按钮放进 `role="group"` 容器并设置 `aria-label`，辅助技术会把它们读作一个操作组。每个按钮都有可见文本，点击结果通过 `aria-live` 区域反馈。

### multiple.vue

```vue
<script setup lang="ts">
// 多个按钮组合：用 role="group" 提供可访问的操作组语义
import { Button, Space } from '@apollo-design/ui';
import { ref } from 'vue';

const lastAction = ref('尚未操作');

function run(action: string): void {
  lastAction.value = action;
}
</script>

<template>
  <div style="display: flex; flex-direction: column; gap: 12px">
    <div role="group" aria-label="文档操作">
      <Space wrap>
        <Button type="primary" @click="run('编辑')">编辑</Button>
        <Button @click="run('复制')">复制</Button>
        <Button @click="run('分享')">分享</Button>
        <Button danger @click="run('删除')">删除</Button>
      </Space>
    </div>
    <p aria-live="polite" style="margin: 0; color: var(--apollo-color-text-secondary); font-size: 12px">
      最近操作：{{ lastAction }}
    </p>
  </div>
</template>
```

## 语义化样式与类 (style-class)

Button 提供 `root` / `content` / `icon` 三个语义槽。`classNames` 拼接类名，`styles` 覆盖对应槽位的样式；示例分别调整根节点圆角与内边距、文本字距和图标颜色。

### style-class.vue

```vue
<script setup lang="ts">
// 语义化样式与类：root / content / icon 三个槽位
import { SearchOutlined } from '@apollo-design/icons';
import type { ButtonProps } from '@apollo-design/ui';
import { Button, Space } from '@apollo-design/ui';

const classNames: ButtonProps['classNames'] = {
  root: 'demo-button-style',
  content: 'demo-button-style__content',
  icon: 'demo-button-style__icon',
};

const styles: ButtonProps['styles'] = {
  root: { borderRadius: '18px', paddingInline: '20px' },
  content: { letterSpacing: '1px' },
  icon: { fontSize: '20px' },
};
</script>

<template>
  <div class="demo-button-style-wrap">
    <Space wrap>
      <Button type="primary" :icon="SearchOutlined" :class-names="classNames" :styles="styles">
        查询
      </Button>
      <Button :class-names="classNames" :styles="styles">默认按钮</Button>
      <Button type="dashed" :class-names="classNames" :styles="styles" disabled>禁用按钮</Button>
    </Space>
    <p class="demo-button-style-wrap__hint">
      root 控制圆角与内边距，content 控制字距，icon 单独设置字号。
    </p>
  </div>
</template>

<style>
.demo-button-style {
  border-color: #d3adf7;
}

.demo-button-style__content {
  font-weight: 500;
}

.demo-button-style-wrap {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.demo-button-style-wrap__hint {
  margin: 0;
  color: var(--apollo-color-text-secondary);
  font-size: 12px;
}
</style>
```
