来源：https://ui.docs.xihanfun.com/components/reasoning

# Reasoning 思考过程 `alpha`

模型的推理片段：默认随写入自动展开，完成后自动收起，用户手动操作过一次后不再自动开合。

<div class="xh-resource-links">
  <a href="https://github.com/XiHanFun/XiHan.UI/tree/dev/ui/packages/engine/headless/src/reasoning" target="_blank" rel="noreferrer">Headless</a>
  <a href="https://github.com/XiHanFun/XiHan.UI/blob/dev/ui/packages/design/styles/css/reasoning.css" target="_blank" rel="noreferrer">Styles</a>
  <a href="https://github.com/XiHanFun/XiHan.UI/tree/dev/ui/packages/adapters/vue/src/components/reasoning" target="_blank" rel="noreferrer">Vue</a>
  <a href="https://github.com/XiHanFun/XiHan.UI/tree/dev/ui/packages/adapters/react/src/components/reasoning" target="_blank" rel="noreferrer">React</a>
  <a href="https://github.com/XiHanFun/XiHan.UI/blob/dev/ui/packages/adapters/web-components/src/elements/reasoning.ts" target="_blank" rel="noreferrer">Web Components</a>
</div>

## 用法

思考时自动展开、思考完成后自动收起；状态文案由组件按是否在思考与时长给出

```vue
<script setup lang="ts">
import {
  XhReasoningContent,
  XhReasoningIndicator,
  XhReasoningLabel,
  XhReasoningRoot,
  XhReasoningTrigger,
} from "@xihan-ui/vue";
import { onBeforeUnmount, onMounted, ref } from "vue";

const streaming = ref(true);
const startTime = ref(Date.now());
const endTime = ref<number | undefined>(undefined);
const text = ref("");

const full = "先看约束：只读一次文件，别改它。再看目标：找出导出面。";
let at = 0;
let timer = 0;
function tick() {
  at = Math.min(at + 2, full.length);
  text.value = full.slice(0, at);
  if (at < full.length) {
    timer = window.setTimeout(tick, 60);
    return;
  }
  endTime.value = Date.now();
  streaming.value = false;
}
// 挂载后才开始追加：<script setup> 顶层在服务端渲染时也执行，那里没有 window
onMounted(() => {
  startTime.value = Date.now();
  tick();
});

onBeforeUnmount(() => window.clearTimeout(timer));

// 名字位不写内容时显示这几句，{seconds} 由组件代入
const translations = {
  label: "思考过程",
  thinking: "正在思考…",
  thoughtFor: "想了 {seconds} 秒",
};
</script>

<template>
  <XhReasoningRoot
    :streaming="streaming"
    :start-time="startTime"
    :end-time="endTime"
    :translations="translations"
  >
    <XhReasoningTrigger>
      <XhReasoningIndicator />
      <XhReasoningLabel />
    </XhReasoningTrigger>
    <XhReasoningContent>{{ text }}</XhReasoningContent>
  </XhReasoningRoot>
</template>
```

```html
<xh-reasoning id="reasoning-basic" streaming>
  <div data-xh-part="root">
    <button data-xh-part="trigger">
      <span data-xh-part="indicator"></span>
      <span data-xh-part="label">正在思考…</span>
    </button>
    <div data-xh-part="content"></div>
  </div>
</xh-reasoning>

<script type="module">
  const panel = document.getElementById("reasoning-basic");
  const body = panel.querySelector('[data-xh-part="content"]');
  const label = panel.querySelector('[data-xh-part="label"]');
  const full = "先看约束：只读一次文件，别改它。再看目标：找出导出面。";

  // 文案是对象，只走 property；{seconds} 由元素代入
  panel.translations = {
    label: "思考过程",
    thinking: "正在思考…",
    thoughtFor: "想了 {seconds} 秒",
  };

  const startTime = performance.timeOrigin + performance.now();
  panel.setAttribute("start-time", String(Math.round(startTime)));

  let at = 0;
  const tick = () => {
    if (!panel.isConnected) return;
    at = Math.min(at + 2, full.length);
    body.textContent = full.slice(0, at);
    if (at < full.length) {
      setTimeout(tick, 60);
      return;
    }
    const endTime = performance.timeOrigin + performance.now();
    panel.setAttribute("end-time", String(Math.round(endTime)));
    panel.removeAttribute("streaming");
    // 状态文案由元素算好，作者把它写进名字位
    label.textContent = panel.statusText;
  };
  tick();
</script>
```

## 组件结构

加粗的是必需部件。

`data-scope="reasoning"`：**`root`** · **`trigger`** · `icon` · `indicator` · `label` · `duration` · **`content`**

## 示例

### 无壳内联形态

ghost 档不占用一块面，开关收为只占文字宽度的小药丸，适合在一段回答中穿插多处

```vue
<script setup lang="ts">
import {
  XhReasoningContent,
  XhReasoningDuration,
  XhReasoningIcon,
  XhReasoningIndicator,
  XhReasoningLabel,
  XhReasoningRoot,
  XhReasoningTrigger,
} from "@xihan-ui/vue";

const notes = [
  {
    id: "a",
    start: 0,
    end: 2400,
    text: "先确认这次只改皮肤：解剖与事件都不动，公开面就只增不减。",
  },
  {
    id: "b",
    start: 0,
    end: 700,
    text: "再看形态轴：ghost 关掉底与描边，缩进由左侧那条竖线接管。",
  },
];
</script>

<template>
  <div class="reasoning-inline-demo">
    <p>把两处思考穿插在同一段回答里，它们不再各自占一张卡：</p>
    <XhReasoningRoot
      v-for="note in notes"
      :key="note.id"
      variant="ghost"
      :start-time="note.start"
      :end-time="note.end"
    >
      <template #default="{ durationMs }">
        <XhReasoningTrigger>
          <XhReasoningIcon />
          <XhReasoningIndicator />
          <XhReasoningLabel>思考过程</XhReasoningLabel>
          <XhReasoningDuration>
            {{ ((durationMs ?? 0) / 1000).toFixed(1) }} 秒
          </XhReasoningDuration>
        </XhReasoningTrigger>
        <XhReasoningContent>{{ note.text }}</XhReasoningContent>
      </template>
    </XhReasoningRoot>
  </div>
</template>

<style scoped>
.reasoning-inline-demo {
  display: flex;
  flex-direction: column;
  gap: var(--xh-space-2);
}
</style>
```

```html
<p>把两处思考穿插在同一段回答里，它们不再各自占一张卡：</p>

<xh-reasoning
  id="reasoning-inline-a"
  variant="ghost"
  start-time="0"
  end-time="2400"
>
  <div data-xh-part="root">
    <button data-xh-part="trigger">
      <span data-xh-part="icon"></span>
      <span data-xh-part="indicator"></span>
      <span data-xh-part="label">思考过程</span>
      <span data-xh-part="duration"></span>
    </button>
    <div data-xh-part="content">
      先确认这次只改皮肤：解剖与事件都不动，公开面就只增不减。
    </div>
  </div>
</xh-reasoning>

<xh-reasoning
  id="reasoning-inline-b"
  variant="ghost"
  start-time="0"
  end-time="700"
>
  <div data-xh-part="root">
    <button data-xh-part="trigger">
      <span data-xh-part="icon"></span>
      <span data-xh-part="indicator"></span>
      <span data-xh-part="label">思考过程</span>
      <span data-xh-part="duration"></span>
    </button>
    <div data-xh-part="content">
      再看形态轴：ghost 关掉底与描边，缩进由左侧那条竖线接管。
    </div>
  </div>
</xh-reasoning>

<script type="module">
  // 名字位写死组件名，秒数交给耗时位：两个排版位各司其职
  for (const id of ["reasoning-inline-a", "reasoning-inline-b"]) {
    const panel = document.getElementById(id);
    const seconds = (panel.durationMs ?? 0) / 1000;
    panel.querySelector('[data-xh-part="duration"]').textContent =
      `${seconds.toFixed(1)} 秒`;
  }
</script>
```

### 语气与尺寸

tone 切换指示符与状态文案的色族，size 切换标题行与正文的几何档；五份都处于思考中，正文自动展开

```vue
<script setup lang="ts">
import {
  XhReasoningContent,
  XhReasoningIndicator,
  XhReasoningLabel,
  XhReasoningRoot,
  XhReasoningTrigger,
} from "@xihan-ui/vue";

const translations = {
  label: "思考过程",
  thinking: "正在思考…",
  thoughtFor: "想了 {seconds} 秒",
};

const rows = [
  { tone: "success", size: "md", label: "success" },
  { tone: "warning", size: "md", label: "warning" },
  { tone: "danger", size: "md", label: "danger" },
  { tone: "brand", size: "sm", label: "sm" },
  { tone: "brand", size: "lg", label: "lg" },
];
</script>

<template>
  <div style="display: flex; flex-direction: column; gap: 12px">
    <XhReasoningRoot
      v-for="row in rows"
      :key="row.label"
      :tone="row.tone"
      :size="row.size"
      :translations="translations"
      streaming
    >
      <XhReasoningTrigger>
        <XhReasoningIndicator />
        <XhReasoningLabel />
      </XhReasoningTrigger>
      <XhReasoningContent>{{ row.label }}：先看约束，再看目标。</XhReasoningContent>
    </XhReasoningRoot>
  </div>
</template>
```

```html
<div style="display: flex; flex-direction: column; gap: 12px">
  <xh-reasoning class="reasoning-axes" tone="success" streaming>
    <div data-xh-part="root">
      <button data-xh-part="trigger">
        <span data-xh-part="indicator"></span>
        <span data-xh-part="label">正在思考…</span>
      </button>
      <div data-xh-part="content">success：先看约束，再看目标。</div>
    </div>
  </xh-reasoning>
  <xh-reasoning class="reasoning-axes" tone="warning" streaming>
    <div data-xh-part="root">
      <button data-xh-part="trigger">
        <span data-xh-part="indicator"></span>
        <span data-xh-part="label">正在思考…</span>
      </button>
      <div data-xh-part="content">warning：先看约束，再看目标。</div>
    </div>
  </xh-reasoning>
  <xh-reasoning class="reasoning-axes" tone="danger" streaming>
    <div data-xh-part="root">
      <button data-xh-part="trigger">
        <span data-xh-part="indicator"></span>
        <span data-xh-part="label">正在思考…</span>
      </button>
      <div data-xh-part="content">danger：先看约束，再看目标。</div>
    </div>
  </xh-reasoning>
  <xh-reasoning class="reasoning-axes" size="sm" streaming>
    <div data-xh-part="root">
      <button data-xh-part="trigger">
        <span data-xh-part="indicator"></span>
        <span data-xh-part="label">正在思考…</span>
      </button>
      <div data-xh-part="content">sm：先看约束，再看目标。</div>
    </div>
  </xh-reasoning>
  <xh-reasoning class="reasoning-axes" size="lg" streaming>
    <div data-xh-part="root">
      <button data-xh-part="trigger">
        <span data-xh-part="indicator"></span>
        <span data-xh-part="label">正在思考…</span>
      </button>
      <div data-xh-part="content">lg：先看约束，再看目标。</div>
    </div>
  </xh-reasoning>
</div>

<script type="module">
  // 文案是对象，只走 property；{seconds} 由元素代入
  for (const panel of document.querySelectorAll(".reasoning-axes"))
    panel.translations = { label: "思考过程", thinking: "正在思考…", thoughtFor: "想了 {seconds} 秒" };
</script>
```

### 受控开合与禁用

open 交给宿主：外部一个按钮统一开合几段思考，自动开合让位；disabled 的一段开关不可按下，停在给定的档位

```vue
<script setup lang="ts">
import {
  XhButton,
  XhReasoningContent,
  XhReasoningIndicator,
  XhReasoningLabel,
  XhReasoningRoot,
  XhReasoningTrigger,
} from "@xihan-ui/vue";
import { ref } from "vue";

const notes = [
  {
    id: "plan",
    start: 0,
    end: 2400,
    text: "先确认这次只改皮肤：解剖与事件都不动，公开面就只增不减。",
  },
  {
    id: "check",
    start: 0,
    end: 900,
    text: "再看形态轴：outline 只留一条描边，底色交回页面。",
  },
];

// 每段各占一位，外面那两颗钮改的是同一份状态
const open = ref([true, false]);

const translations = {
  label: "思考过程",
  thinking: "正在思考…",
  thoughtFor: "想了 {seconds} 秒",
};
</script>

<template>
  <div style="display: flex; flex-direction: column; gap: 12px">
    <div style="display: flex; gap: 8px">
      <XhButton size="sm" variant="outline" @click="open = open.map(() => true)">
        全部展开
      </XhButton>
      <XhButton size="sm" variant="outline" @click="open = open.map(() => false)">
        全部收起
      </XhButton>
    </div>

    <XhReasoningRoot
      v-for="(note, index) in notes"
      :key="note.id"
      v-model:open="open[index]"
      variant="outline"
      :start-time="note.start"
      :end-time="note.end"
      :translations="translations"
    >
      <XhReasoningTrigger>
        <XhReasoningIndicator />
        <XhReasoningLabel />
      </XhReasoningTrigger>
      <XhReasoningContent>{{ note.text }}</XhReasoningContent>
    </XhReasoningRoot>

    <!-- 归档的那一段：开关按不动，正文停在收起 -->
    <XhReasoningRoot
      variant="outline"
      disabled
      :open="false"
      :start-time="0"
      :end-time="12000"
      :translations="translations"
    >
      <XhReasoningTrigger>
        <XhReasoningIndicator />
        <XhReasoningLabel />
      </XhReasoningTrigger>
      <XhReasoningContent>这一段已归档，正文不再展开。</XhReasoningContent>
    </XhReasoningRoot>
  </div>
</template>
```

```html
<div id="reasoning-controlled-demo" style="display: flex; flex-direction: column; gap: 12px">
  <div style="display: flex; gap: 8px">
    <xh-button size="sm" variant="outline">
      <button data-xh-part="root" id="reasoning-expand-all">全部展开</button>
    </xh-button>
    <xh-button size="sm" variant="outline">
      <button data-xh-part="root" id="reasoning-collapse-all">全部收起</button>
    </xh-button>
  </div>

  <xh-reasoning
    class="reasoning-controlled"
    variant="outline"
    open
    start-time="0"
    end-time="2400"
  >
    <div data-xh-part="root">
      <button data-xh-part="trigger">
        <span data-xh-part="indicator"></span>
        <span data-xh-part="label"></span>
      </button>
      <div data-xh-part="content">
        先确认这次只改皮肤：解剖与事件都不动，公开面就只增不减。
      </div>
    </div>
  </xh-reasoning>

  <xh-reasoning class="reasoning-controlled" variant="outline" start-time="0" end-time="900">
    <div data-xh-part="root">
      <button data-xh-part="trigger">
        <span data-xh-part="indicator"></span>
        <span data-xh-part="label"></span>
      </button>
      <div data-xh-part="content">再看形态轴：outline 只留一条描边，底色交回页面。</div>
    </div>
  </xh-reasoning>

  <!-- 归档的那一段：开关按不动，正文停在收起 -->
  <xh-reasoning variant="outline" disabled start-time="0" end-time="12000">
    <div data-xh-part="root">
      <button data-xh-part="trigger">
        <span data-xh-part="indicator"></span>
        <span data-xh-part="label"></span>
      </button>
      <div data-xh-part="content">这一段已归档，正文不再展开。</div>
    </div>
  </xh-reasoning>
</div>

<script type="module">
  const demo = document.getElementById("reasoning-controlled-demo");
  const panels = demo.querySelectorAll(".reasoning-controlled");
  const translations = {
    label: "思考过程",
    thinking: "正在思考…",
    thoughtFor: "想了 {seconds} 秒",
  };

  // 文案是对象，只走 property；{seconds} 由元素代入
  for (const panel of demo.querySelectorAll("xh-reasoning")) {
    panel.translations = translations;
  }

  // 开合归宿主：元素只发意图，落值全在这里
  for (const panel of panels) {
    panel.addEventListener("open-change", (event) => {
      panel.open = event.detail.open;
    });
  }

  // 外面那两颗钮改的是同一份状态
  document.getElementById("reasoning-expand-all").addEventListener("click", () => {
    for (const panel of panels) panel.open = true;
  });
  document.getElementById("reasoning-collapse-all").addEventListener("click", () => {
    for (const panel of panels) panel.open = false;
  });
</script>
```

## 设计指引

### 何时使用

- 展示推理模型输出的思考过程，且它边生成边显示。
- 希望读者可以回看推理过程，但默认不占版面。

### 何时不用

- 展示一次工具调用时，使用[工具调用](./tool-call)，两者共用同一台状态机但正文形态不同。
- 内容不是散文而是结构化数据时，属于工具调用的参数与结果。

### 特性

- 自动开合与[工具调用](./tool-call)是同一台状态机：锁存依靠转移的放置位置，不依靠布尔位，用户点击过一次之后阶段变化就不再触发自动开合。
- 思考时长由两个时刻计算，任一缺席即无法计算：流被中止时兜底收尾不写结束时刻，推理块只有起点没有终点，这一情况必须被处理。
- 名称与时长都排在开关内，“思考过程，用时 12 秒”整句构成开关的可访问名称。
- 状态文案由组件提供：进行中显示“在想”的文案，完成后把秒数代入 `thoughtFor` 的 `{seconds}`，无法计算时长时回落到折叠区的名称。名称位不写内容时显示的就是它。
- 形态三档：`outline` 描边、`subtle` 底色分区（默认档）、`ghost` 无壳内联。一段回答中穿插多处思考时使用 `ghost`，它不占一块面，开关收为只占文字宽度的小圆角块。
- 开合有动画：展开与收起是行高与内缩同帧动画，收起在动画完成后才真正隐藏。

### 组合

- 正文使用[流式正文](./markdown-stream)：思考过程是散文，与工具调用的等宽结构块不同。正文放在一个容器内：展开动画测量的是第一行的行高，散落的多个兄弟节点无法正确收起。
- 多段推理并排且一次只展开一段时使用[手风琴](./accordion)。
- 需要让“进行中 → 完成”被读屏播报时，把会话级的活动区域放在推理块外，由它读出结果。

### 最佳实践

- 完成后显示时长，读者据此判断是否值得展开。
- 默认收起。思考过程是给需要查看的人看的，不是回答本身。

### 反模式

- 把思考过程当作回答显示：两者混在一起时读者分不清结论。
- 用它承载工具调用的参数与结果：正文排版是散文形态，等宽结构块会挤在一起。

## API 参考

### 产物

| 层 | 值 |
| --- | --- |
| 自定义元素 | `<xh-reasoning>` |
| Vue 组件 | `XhReasoningContent` `XhReasoningDuration` `XhReasoningIcon` `XhReasoningIndicator` `XhReasoningLabel` `XhReasoningRoot` `XhReasoningTrigger` |
| 组合式函数 | `useReasoning` |
| 状态机 | 无，`connect` 直接由 props 算属性 |
| 皮肤 | `@xihan-ui/styles/reasoning.css` |

### Props

| 属性 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `endTime` | `number` |  | 思考结束的时刻。可能缺席：流被中止时兜底收尾不写该字段。 |
| `size` | `Size` |  |  |
| `startTime` | `number` |  | 开始思考的时刻，毫秒时间戳。 |
| `streaming` | `boolean` |  | 仍在思考。适配器把它折叠为状态机的 running。 |
| `tone` | `Tone` |  |  |
| `translations` | `Partial<ReasoningTranslations>` |  |  |
| `variant` | `ControlVariant` |  | 形态：outline 描边、subtle 底色分区、ghost 无壳内联。默认 subtle。 |

### 事件

自定义元素将载荷放在 `detail`；Vue 使用同名 emit。

| 事件 | 载荷 | 说明 |
| --- | --- | --- |
| `open-change` | `ToolCallOpenChangeDetails` | 开合变化；detail 为 `{ open: boolean, source: 'user' \| 'auto' \| 'api' }` |

### 插槽

仅列出带载荷的插槽。

| Vue 组件 | 插槽 | 载荷 | 说明 |
| --- | --- | --- | --- |
| `XhReasoningRoot` | `default` | `ReasoningRootSlotProps` |  |

### 状态

公开状态写入 `data-state`。

| 部件 | 取值 |
| --- | --- |
| `root` | 'open' \| 'closed' |
| `trigger` | 'open' \| 'closed' |
| `indicator` | 'open' \| 'closed' |
| `content` | 'open' \| 'closed' |

### connect API

`getXxxProps()` 返回对应部件的宿主属性。

| 成员 | 类型 | 说明 |
| --- | --- | --- |
| `open` | `boolean` |  |
| `streaming` | `boolean` |  |
| `disabled` | `boolean` |  |
| `durationMs` | `number \| undefined` | 思考时长，毫秒；两个时刻任一缺席即 undefined。 |
| `statusText` | `string` | 当前应显示的状态文案，已按 streaming 与时长选定。 |
| `setOpen` | `(next: boolean) => void` |  |
| `getRootProps` | `() => T['element']` |  |
| `getTriggerProps` | `() => T['button']` |  |
| `getIconProps` | `() => T['element']` |  |
| `getIndicatorProps` | `() => T['element']` |  |
| `getLabelProps` | `() => T['element']` |  |
| `getDurationProps` | `() => T['element']` |  |
| `getContentProps` | `() => T['element']` |  |

## 无障碍

### 键盘

规格出处：[W3C APG](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/)

| 按键 | 生效条件 | 行为 |
| --- | --- | --- |
| `Enter` / `Space` | 焦点在折叠开关上且未禁用 | 展开或收起思考正文，并把自动开合永久停用 |
| `Enter` / `Space` | 按住折叠开关且未禁用 | 按住期间 trigger 投影 data-pressed，与指针 :active 同一副按压面（disclosure trigger 只换面不缩放）；抬起、失焦或转禁用撤下。思考中照常接 |

### ARIA

以下属性由 `connect` 生成。

| 部件 | 属性 | 值 |
| --- | --- | --- |
| `trigger` | `aria-controls` | `content` 部件的 id |
| `trigger` | `aria-expanded` | 'true' \| 'false' |
| `icon` | `aria-hidden` | 'true' |
| `indicator` | `aria-hidden` | 'true' |
| `content` | `aria-labelledby` | `trigger` 部件的 id |
| `content` | `role` | 'region' |

- 开关带 `aria-expanded` 与 `aria-controls`，正文区是 `role=region` 且由开关命名。
- 不另发 `aria-label`：另发会覆盖节点内的文字，两者不一致时读屏读出的与屏幕不符。
- 组件自身不开活动区域：整段思考每来一个字都播报会淹没读屏。

## 样式参考

### 皮肤

`@xihan-ui/styles/reasoning.css` 使用 `[data-scope="reasoning"][data-part="root"]` 部件选择器，位于 `xihan.components` 与 `xihan.motion` 层。覆盖样式使用 `xihan.overrides`。

`forced-colors: active` 下另有一套规则：颜色交给系统，边框与状态标记改用系统色关键字。

### 数据属性

由 `connect` 生成；条件不成立时不输出无值属性。

| 部件 | 属性 | 值 |
| --- | --- | --- |
| `root` | `data-disabled` | ''（条件成立时才出现） |
| `root` | `data-size` | props.size |
| `root` | `data-state` | 'open' \| 'closed' |
| `root` | `data-streaming` | ''（条件成立时才出现） |
| `root` | `data-tone` | props.tone |
| `root` | `data-variant` | props.variant |
| `trigger` | `data-disabled` | ''（条件成立时才出现） |
| `trigger` | `data-pressed` | ''（条件成立时才出现） |
| `trigger` | `data-state` | 'open' \| 'closed' |
| `trigger` | `data-streaming` | ''（条件成立时才出现） |
| `trigger` | `data-xh-action-control` | '' |
| `trigger` | `data-xh-action-display` | 'always' |
| `trigger` | `data-xh-action-profile` | 'disclosure-trigger' |
| `trigger` | `data-xh-action-size` | props.size |
| `trigger` | `data-xh-action-variant` | 'ghost' |
| `icon` | `data-streaming` | ''（条件成立时才出现） |
| `indicator` | `data-state` | 'open' \| 'closed' |
| `label` | `data-streaming` | ''（条件成立时才出现） |
| `duration` | `data-streaming` | ''（条件成立时才出现） |
| `content` | `data-state` | 'open' \| 'closed' |

<!-- xh-component-tokens:start -->
### CSS 变量

本组件公开覆盖槽由独立皮肤的实际消费位生成；默认来源、作用部件和状态均与 CSS 同源。

| 变量 | 部件 | CSS 属性 | 状态 | 默认来源 | 说明 |
| --- | --- | --- | --- | --- | --- |
| `--xh-reasoning-bg` | `root` | `background` | `default`<br>`variant=outline` | `--xh-bg-subtle`<br>`--xh-bg-surface` | reasoning 的 root 部件 background 覆盖槽。 |
| `--xh-reasoning-border` | `root` | `border` | `variant=outline` | `--xh-border-default` | reasoning 的 root 部件 border 覆盖槽。 |
| `--xh-reasoning-content-fg` | `content` | `color` | `default` | `--xh-fg-muted` | reasoning 的 content 部件 color 覆盖槽。 |
| `--xh-reasoning-content-font-size` | `content` | `font-size` | `default` | `--xh-text-secondary-size` | reasoning 的 content 部件 font-size 覆盖槽。 |
| `--xh-reasoning-content-leading` | `content` | `line-height` | `default` | `--xh-text-prose-leading` | reasoning 的 content 部件 line-height 覆盖槽。 |
| `--xh-reasoning-content-pe` | `content` | `padding-inline-end` | `default` | `--xh-reasoning-px` | reasoning 的 content 部件 padding-inline-end 覆盖槽。 |
| `--xh-reasoning-content-ps` | `content` | `padding-inline-start` | `default` | `--xh-space-3` | reasoning 的 content 部件 padding-inline-start 覆盖槽。 |
| `--xh-reasoning-duration-fg` | `duration` | `color` | `default` | `--xh-fg-subtle` | reasoning 的 duration 部件 color 覆盖槽。 |
| `--xh-reasoning-duration-font-size` | `duration` | `font-size` | `default` | `--xh-text-caption-size` | reasoning 的 duration 部件 font-size 覆盖槽。 |
| `--xh-reasoning-font-size` | `trigger` | `font-size` | `default` | `--xh-_reasoning-font-size` | reasoning 的 trigger 部件 font-size 覆盖槽。 |
| `--xh-reasoning-icon-fg` | `icon` | `color` | `default` | `--xh-fg-subtle` | reasoning 的 icon 部件 color 覆盖槽。 |
| `--xh-reasoning-icon-size` | `root` | `--xh-icon-size` | `default` | `--xh-_reasoning-icon-size` | reasoning 的 root 部件 --xh-icon-size 覆盖槽。 |
| `--xh-reasoning-icon-streaming-fg` | `icon` | `color` | `streaming` | `--xh-fg-muted` | reasoning 的 icon 部件 color 覆盖槽。 |
| `--xh-reasoning-indicator-fg` | `indicator` | `color` | `default` | `--xh-fg-subtle` | reasoning 的 indicator 部件 color 覆盖槽。 |
| `--xh-reasoning-label-font-weight` | `label` | `font-weight` | `default` | `--xh-text-label-weight` | reasoning 的 label 部件 font-weight 覆盖槽。 |
| `--xh-reasoning-label-streaming-fg` | `label` | `color` | `@media (prefers-reduced-motion: reduce)`<br>`@media print`<br>`motion=reduce`<br>`streaming`<br>`where([data-motion='reduce'])` | `--xh-fg-default` | reasoning 的 label 部件 color 覆盖槽。 |
| `--xh-reasoning-px` | `content`<br>`trigger` | `margin-inline-start`<br>`padding-inline`<br>`padding-inline-end` | `default` | `--xh-_reasoning-px` | reasoning 的 content、trigger 部件 margin-inline-start、padding-inline、padding-inline-end 覆盖槽。 |
| `--xh-reasoning-py` | `content`<br>`trigger` | `padding-block`<br>`padding-block-end` | `@keyframes xh-disclosure-collapse`<br>`@keyframes xh-disclosure-expand`<br>`default`<br>`xh-action-profile=disclosure-trigger` | `--xh-_reasoning-py` | reasoning 的 content、trigger 部件 padding-block、padding-block-end 覆盖槽。 |
| `--xh-reasoning-radius` | `root` | `border-radius` | `default` | `--xh-shape-surface` | reasoning 的 root 部件 border-radius 覆盖槽。 |
| `--xh-reasoning-rail` | `content` | `border-inline-start` | `default` | `--xh-border-subtle` | reasoning 的 content 部件 border-inline-start 覆盖槽。 |
| `--xh-reasoning-rail-inset` | `content` | `margin-inline-start` | `default` | `--xh-reasoning-px` | reasoning 的 content 部件 margin-inline-start 覆盖槽。 |
| `--xh-reasoning-rail-width` | `content` | `border-inline-start` | `default` | `--xh-stroke-thin` | reasoning 的 content 部件 border-inline-start 覆盖槽。 |
| `--xh-reasoning-shadow` | `root` | `box-shadow` | `default`<br>`tone` | `0 0 0 transparent`<br>`none` | reasoning 的 root 部件 box-shadow 覆盖槽。 |
| `--xh-reasoning-shimmer-duration` | `label` | `animation` | `streaming` | `--xh-shimmer-duration` | reasoning 的 label 部件 animation 覆盖槽。 |
| `--xh-reasoning-shimmer-from` | `label` | `background-image` | `streaming` | `--xh-fg-subtle` | reasoning 的 label 部件 background-image 覆盖槽。 |
| `--xh-reasoning-shimmer-to` | `label` | `background-image` | `streaming` | `--xh-fg-default` | reasoning 的 label 部件 background-image 覆盖槽。 |
| `--xh-reasoning-tone-bar` | `root` | `box-shadow` | `tone` | `--xh-stroke-thick` | reasoning 的 root 部件 box-shadow 覆盖槽。 |
| `--xh-reasoning-tone-fg` | `root` | `box-shadow` | `tone` | `--xh-_tone-soft` | reasoning 的 root 部件 box-shadow 覆盖槽。 |
| `--xh-reasoning-trigger-bg-hover` | `trigger` | `background-color` | `disabled`<br>`hover`<br>`loading`<br>`not([data-disabled])`<br>`not([data-loading])` | `--xh-_action-variant-bg-hover` | reasoning 的 trigger 部件 background-color 覆盖槽。 |
| `--xh-reasoning-trigger-fg` | `trigger` | `color` | `default`<br>`disabled`<br>`hover`<br>`is(:active, [data-pressed])`<br>`loading`<br>`not([data-disabled])`<br>`not([data-loading])`<br>`pressed` | `--xh-fg-muted` | reasoning 的 trigger 部件 color 覆盖槽。 |
| `--xh-reasoning-trigger-gap` | `trigger` | `gap` | `default` | `--xh-space-2` | reasoning 的 trigger 部件 gap 覆盖槽。 |
| `--xh-reasoning-trigger-radius` | `root`<br>`trigger` | `border-radius` | `variant=ghost` | `--xh-shape-control` | reasoning 的 root、trigger 部件 border-radius 覆盖槽。 |
<!-- xh-component-tokens:end -->

### 动效

关键帧 `xh-reasoning-fade-in` · `xh-reasoning-shimmer` 随皮肤自带，不引用别处文件里的名字；共享关键帧 `xh-disclosure-collapse` · `xh-disclosure-expand` 由 `family/motion.css` 提供，皮肤 `@import` 它，单独引入仍成立；`color` · `rotate` 走 `transition` 过渡。时长与缓动读[动效令牌](../guide/motion)，改令牌即改全局节奏。

皮肤之外还有一段：退场由适配器的退场闸门把关，动画播完才真收起。

`prefers-reduced-motion: reduce` 下本组件另有降级规则。

### RTL

皮肤用逻辑属性排布（`inline-start` 一族），`dir="rtl"` 下自动镜像。
