# 主题定制

组件库的所有视觉表现都由 `--m-*` 形式的 CSS 自定义属性驱动。这套令牌定义在 `miao-design/style.css` 的 `:root` 中，**严格对齐 MUI v5 的默认主题**：调色板取自 MUI `createPalette`，阴影取自 `shadows.js`，缓动曲线与时长取自 `createTransitions`。

这意味着两件事：一是换肤只需要覆写变量；二是如果你的团队本来就在用 MUI 的设计语言，视觉可以直接对齐。

## 语义色

六组语义色，每组包含五个变量：

| 变量 | 含义 |
| --- | --- |
| `--m-<color>` | 主色（`main`） |
| `--m-<color>-hover` | 悬停态（`dark`） |
| `--m-<color>-light` | 浅色变体（`light`） |
| `--m-<color>-contrast` | 主色之上的文字色 |
| `--m-<color>-rgb` | 以空格分隔的 RGB 通道，用于 `rgb(var(--m-primary-rgb) / 0.2)` 这类透明度写法 |

<ColorSwatches />

## 背景与浮层

<ColorSwatches preset="surface" />

`--m-popup` 专门给下拉菜单、选择器、日期面板这类临时浮层使用（暗色下是 `#242424` 而非纯黑），`--m-card` 是兼容旧代码保留的卡片底。

## 交互态

<ColorSwatches preset="state" />

## 文字与边框

<ColorSwatches preset="text" />

## 形状、排版与尺寸

```css
:root {
  --m-radius: 6px;                 /* 全局圆角（theme.shape.borderRadius） */
  --m-font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif;
  --m-font-weight-medium: 500;

  --m-size-default: 36px;          /* 输入框 / 下拉 / 按钮等默认高度 */
  --m-size-small: 28px;
  --m-size-large: 48px;
}
```

## 缓动与时长

组件的过渡动画统一使用这四个缓动函数与六个时长，做自定义动效时直接复用它们即可保持节奏一致。

| 变量 | 值 |
| --- | --- |
| `--m-ease-in-out` | `cubic-bezier(0.4, 0, 0.2, 1)` |
| `--m-ease-out` | `cubic-bezier(0, 0, 0.2, 1)` |
| `--m-ease-in` | `cubic-bezier(0.4, 0, 1, 1)` |
| `--m-ease-sharp` | `cubic-bezier(0.4, 0, 0.6, 1)` |
| `--m-dur-shortest` ~ `--m-dur-complex` | `150ms` / `200ms` / `250ms` / `300ms` / `375ms` |
| `--m-dur-entering` / `--m-dur-leaving` | `225ms` / `195ms`（JS 读取，用于弹出面板） |

## 阴影

阴影直接取自 MUI 的 `shadows.js`。注意源码只定义了 `0 / 1 / 2 / 3 / 4 / 6 / 8 / 12 / 16 / 24` 这几档，其余档位不存在。

| 变量 | 用途 |
| --- | --- |
| `--m-shadow-0` ~ `--m-shadow-24` | 通用 elevation |
| `--m-dialog-shadow` | 对话框纸面（等于 `--m-shadow-24`） |
| `--m-dropdown-shadow` | 菜单 / 浮层（Vuetify MD3 的 6dp 双层柔影） |

## 层级

| 变量 | 值 | 用途 |
| --- | --- | --- |
| `--m-z-modal` | `1300` | 模态层（Dialog / Drawer） |
| `--m-z-menu` | `1301` | 菜单 / 下拉面板 |
| `--m-z-tooltip` | `1500` | 文字提示 |
| `--m-z-toast` | `1600` | Toast（最顶层） |

## 覆写令牌

在业务样式里重新声明同名变量即可，不需要 `!important`，也不需要修改组件库源码：

```css
/* 把品牌主色换成青色系 */
:root {
  --m-primary: #0f766e;
  --m-primary-hover: #115e59;
  --m-primary-light: #14b8a6;
  --m-primary-rgb: 15 118 110;
}
```

或者做一个作用域内的局部主题：

```css
/* 只在这块区域里换掉圆角与主色 */
.brand-panel {
  --m-radius: 12px;
  --m-primary: #7c3aed;
  --m-primary-rgb: 124 58 237;
}
```

## 深色模式

暗色令牌定义在 `:root[data-theme='dark']` 上，因此只需要在根元素（或任意局部容器）上挂一个属性：

```ts
// 跟随系统
const dark = window.matchMedia('(prefers-color-scheme: dark)').matches
document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light')
```

```ts
// 手动切换
function toggleDark(on: boolean) {
  document.documentElement.setAttribute('data-theme', on ? 'dark' : 'light')
}
```

::: tip 暗色下的取值变化
暗色模式并不是简单地把颜色压暗：语义色会切换为 MUI dark palette 的对应值（例如 `--m-primary` 从 `#1976d2` 变成 `#90caf9`），`-contrast` 变成深色文字，阴影的 alpha 会加重。业务侧不要硬编码浅色态的值。
:::

::: tip 与框架主题联动
本站在用 VitePress，它的深色模式类名是 `html.dark`，而组件库认的是 `data-theme="dark"`。站点的做法是在 `Layout.vue` 里监听 `isDark` 并同步属性，让文档站里的真实组件跟随站点外观切换 —— 这是一种通用做法，任何框架都可以照搬。
:::

## 组件级变量

除全局令牌外，部分组件会把自己的 prop 下发给局部变量，方便在不改动 prop 的情况下做样式覆盖：

| 变量 | 来源 | 含义 |
| --- | --- | --- |
| `--m-control-radius` | Input / Select / DatePicker 的 `radius` | 触发区圆角 |
| `--m-panel-radius` | Select / DatePicker / Dropdown 的 `panelRadius` | 弹出面板圆角 |
| `--m-dialog-radius` | Dialog 的 `radius` | 对话框圆角 |
| `--m-divider-color` / `--m-divider-size` / `--m-divider-gap` | Divider 的 `color` / `size` / `gap` | 分割线颜色 / 粗细 / 间距 |
| `--m-slider-percent` | Slider | 已填充行程百分比 |
| `--m-slider-rail-h` / `--m-slider-thumb` | Slider 的 `trackSize` / `thumbSize` | 轨道粗细 / 拇指直径 |
| `--m-ripple-duration` / `--m-ripple-initial-scale` | `v-ripple` 的 `duration` / `initialScale` | 涟漪时长 / 起始缩放 |
| `--m-toast-duration` / `--m-toast-offset` | Toast | 自动关闭时长 / 角位堆叠间距 |

## 下一步

- [AI 阅读](/guide/ai) —— 让 AI 直接读懂这套令牌并帮你写主题代码。
- [常见问题](/guide/faq) —— 样式不生效、弹层被裁剪等高频问题。
