Skip to content

如何支持主题换肤功能

本文介绍开发 Neo 平台自定义组件时,如何正确使用主题 CSS 变量来实现换肤能力。

在线示例

点击查看 主题换肤在线 Demo,可交互切换品牌色、按钮色、导航色,实时预览主题 CSS 变量的使用效果。

目录


一、核心原则

✅ 推荐做法

所有组件应通过 CSS 变量var(--xxx))来消费主题色:

  • 自动响应运行时主题切换,无需修改任何业务代码
  • 保持代码简洁和可维护性
  • 通过 rgba(var(--xxx-rgb), opacity) 实现半透明效果
scss
/* ✅ 正确:使用 CSS 变量 */
.my-button {
  background-color: var(--color-button-filled-primary, #0564f5);
  color: var(--color-button-filled-primary-color);
}

❌ 禁止做法

  • 硬编码颜色值(如 #0564F5rgb(5,100,245) 等品牌相关色值直接写死)
  • ColorsConfig / getNeoContext('ColorsConfig') 读取颜色值后以内联 style= 方式写入组件
  • 绕过 ThemeHelper 直接操作 DOM 注入 CSS 变量
scss
/* ❌ 错误:硬编码 — 切换主题后此颜色不变! */
.btn-primary {
  background-color: #0564F5;
  border-color: #0564F5;
}

二、可用的主题 CSS 变量(含默认色值)

说明:以下为 setBaseColor('#0564f5') 时的默认值。实际运行时由 ThemeHelper API 动态计算并注入,不同主题下取值不同。每个颜色变量均有对应的 -rgb 伴侣变量(纯数值格式,如 5, 100, 245),用于 rgba() 半透明场景。

2.1 品牌核心变量

CSS 变量-rgb 伴侣变量默认色值说明
--brand-color--brand-color-rgb#0564f5品牌主色,所有品牌色体系的根源
--brand-color-hover--brand-color-hover-rgb#1a7aff品牌 Hover 色,各元素 hover 交互态
--brand-color-shade--brand-color-shade-rgb#004ccf品牌深色变体,active / 按下 / 选中态
--brand-color-tint--brand-color-tint-rgb#e6f4ff品牌最浅变体,大面积背景底色
--brand-color-tint-light#a8d7ff--brand-color-tint-light-rgb品牌次浅变体,浅色背景 / hover 背景
--brand-color-disabled--brand-color-disabled-rgb#80bfff品牌禁用态

品牌色阶变量(10 个)

品牌色阶 --brand-color-1 ~ --brand-color-10 对应 @ant-design/colorsgenerate() 生成的 10 级色板。1=最浅 → 10=最深--brand-color-6 为主品牌色。每个变量均有对应的 -rgb 伴侣变量。

CSS 变量-rgb 伴侣变量默认色值说明
--brand-color-1--brand-color-1-rgb#e6f4ff品牌色阶 1 — 最浅色,大面积浅底色
--brand-color-2--brand-color-2-rgb#a8d7ff品牌色阶 2 — 次浅色,轻量浅底色
--brand-color-3--brand-color-3-rgb#91caff品牌色阶 3 — 浅色区,hover 背景等
--brand-color-4--brand-color-4-rgb#80bfff品牌色阶 4 — 禁用态底色
--brand-color-5--brand-color-5-rgb#1a7aff品牌色阶 5 — 次主色,hover 交互态
--brand-color-6--brand-color-6-rgb#0564f5品牌色阶 6 — ★ 主品牌色
--brand-color-7--brand-color-7-rgb#004ccf品牌色阶 7 — 加深色,active 按下态
--brand-color-8--brand-color-8-rgb#003aa6品牌色阶 8 — 深色变体,强强调
--brand-color-9--brand-color-9-rgb#00297a品牌色阶 9 — 深色区,文字高对比
--brand-color-10--brand-color-10-rgb#001a4d品牌色阶 10 — 最深色,极端强调

2.2 背景 Background(6 个变量)

CSS 变量-rgb 伴侣变量级联自说明
--color-bg-brand--color-bg-brand-rgbvar(--brand-color)品牌主色背景,强品牌感区块底色
--color-bg-brand-hover--color-bg-brand-hover-rgbvar(--brand-color-hover)品牌 Hover 背景,hover 交互态
--color-bg-brand-shade--color-bg-brand-shade-rgbvar(--brand-color-shade)品牌深色背景,重点强调区域底色
--color-bg-brand-tint--color-bg-brand-tint-rgbvar(--brand-color-tint)品牌浅色背景,卡片 / 选中行轻量底色
--color-bg-brand-tint-light--color-bg-brand-tint-light-rgbvar(--brand-color-tint-light)品牌次浅色背景,hover 态浅底色
--color-bg-brand-disabled--color-bg-brand-disabled-rgbvar(--brand-color-disabled)品牌禁用态背景

每个变量均有对应的 -rgb 伴侣变量,如 --color-bg-brand-rgb

2.3 边框 Border(6 个变量)

CSS 变量-rgb 伴侣变量级联自说明
--color-border-brand--color-border-brand-rgbvar(--brand-color)品牌主色边框,input focus / 选中边框
--color-border-brand-hover--color-border-brand-hover-rgbvar(--brand-color-hover)品牌 Hover 边框
--color-border-brand-shade--color-border-brand-shade-rgbvar(--brand-color-shade)品牌深色边框
--color-border-brand-tint--color-border-brand-tint-rgbvar(--brand-color-tint)品牌浅色边框
--color-border-brand-tint-light--color-border-brand-tint-light-rgbvar(--brand-color-tint-light)品牌次浅色边框
--color-border-brand-disabled--color-border-brand-disabled-rgbvar(--brand-color-disabled)品牌禁用态边框

2.4 图标 Icon(7 个变量)

CSS 变量-rgb 伴侣变量级联自说明
--color-icon-brand--color-icon-brand-rgbvar(--brand-color)图标主色,功能图标正常态
--color-icon-brand-hover--color-icon-brand-hover-rgbvar(--brand-color-hover)图标 Hover 色
--color-icon-brand-shade--color-icon-brand-shade-rgbvar(--brand-color-shade)图标深色,active 态
--color-icon-brand-tint--color-icon-brand-tint-rgbvar(--brand-color-tint)图标浅色
--color-icon-brand-tint-light--color-icon-brand-tint-light-rgbvar(--brand-color-tint-light)图标次浅色
--color-icon-fill-brand-link--color-icon-fill-brand-link-rgbvar(--brand-color-shade)图标链接色,可点击图标 hover 交互色
--color-icon-brand-disabled--color-icon-brand-disabled-rgbvar(--brand-color-disabled)图标禁用色

2.5 文本 & 文字链接 Text(7 个变量)

CSS 变量-rgb 伴侣变量级联自说明
--color-text-brand--color-text-brand-rgbvar(--brand-color)品牌色文字,关键数据 / 主标题 / 链接基础色
--color-text-brand-hover--color-text-brand-hover-rgbvar(--brand-color-hover)品牌 Hover 文字色
--color-text-brand-shade--color-text-brand-shade-rgbvar(--brand-color-shade)品牌深色文字,链接 active 态
--color-text-brand-tint--color-text-brand-tint-rgbvar(--brand-color-tint)品牌浅色文字底
--color-text-brand-tint-light--color-text-brand-tint-light-rgbvar(--brand-color-tint-light)品牌次浅色文字
--color-text-brand-disabled--color-text-brand-disabled-rgbvar(--brand-color-disabled)品牌禁用态文字色
--color-text-brand-link--color-text-brand-link-rgbvar(--brand-color-shade)文字链接色,hover 加深效果

2.6 按钮色变量

核心说明--color-button-filled-primary-* 系列变量用于实心底色按钮背景色,同时可复用于白色背景按钮的 border 边框outline 描边text 文字的颜色。即 fill / border / outline / text 四种按钮形态共用这一套变量。

背景色(5 个,均配有 -rgb 伴侣)

CSS 变量-rgb 伴侣变量级联自说明
--color-button-filled-primary--color-button-filled-primary-rgbvar(--color-bg-brand)正常态背景色(★ 同时可用于 border/outline/text 颜色)
--color-button-filled-primary-hover--color-button-filled-primary-hover-rgbvar(--color-bg-brand-hover)鼠标悬停背景色
--color-button-filled-primary-active--color-button-filled-primary-active-rgbvar(--color-bg-brand-shade)点击按下背景色
--color-button-filled-primary-tint--color-button-filled-primary-tint-rgbvar(--color-bg-brand-tint)浅色背景
--color-button-filled-primary-disabled--color-button-filled-primary-disabled-rgbvar(--color-bg-brand-disabled)禁用态背景色

文字色(3 个)

CSS 变量级联自说明
--color-button-filled-primary-colorvar(--color-neutral-0)正常态文字色(自动对比色)
--color-button-filled-primary-color-hovervar(--color-neutral-0)悬停文字色
--color-button-filled-primary-color-disabledvar(--color-text-aid)禁用文字色

次按钮变量(3 个,均配有 -rgb 伴侣)

CSS 变量-rgb 伴侣变量说明
--color-button-filled-primary-secondary--color-button-filled-primary-secondary-rgb★ 次按钮 — 正常态背景(降一级变浅,colors[4])
--color-button-filled-primary-secondary-hover--color-button-filled-primary-secondary-hover-rgb★ 次按钮 — 悬停态背景(比 hover 降一级变浅)
--color-button-filled-primary-secondary-disabled--color-button-filled-primary-secondary-disabled-rgb★ 次按钮 — 禁用态背景(比 disabled 降一级变浅)

2.7 导航 Banner 色变量(11 个变量)

CSS 变量-rgb 伴侣变量说明
--brand-nav-header-bg--brand-nav-header-bg-rgb导航栏背景色
--brand-nav-header-font--brand-nav-header-font-rgb导航栏标题 / 正文文字色(自动识别深浅色)
--brand-nav-header-font-hover--brand-nav-header-font-hover-rgb导航栏文字 Hover 态
--brand-nav-header-icon--brand-nav-header-icon-rgb导航栏图标色(返回 / 关闭等)
--brand-nav-header-icon-hover--brand-nav-header-icon-hover-rgb导航栏图标 Hover 态
--brand-nav-header-launcher-icon--brand-nav-header-launcher-icon-rgb启动器图标色(使用品牌色)
--brand-nav-header-select--brand-nav-header-select-rgb选中项高亮色(使用品牌深色变体)
--brand-nav-header-select-bg--brand-nav-header-select-bg-rgb导航栏选中项背景色
--brand-nav-header-elem-opacity--brand-nav-header-elem-opacity-rgb导航元素半透明层
--brand-nav-header-elem-hover--brand-nav-header-elem-hover-rgb导航元素悬停背景色
--brand-nav-header-placeholder--brand-nav-header-placeholder-rgb导航栏输入框占位符色

2.8 -rgb 伴侣变量使用说明

每个颜色变量都配有对应的 -rgb 伴侣变量,专用于 rgba() 半透明场景:

格式:rgba(var(--xxx-rgb), <alpha>)
示例:rgba(var(--brand-color-rgb), 0.15)
scss
/* ✅ 半透明覆盖层 */
.overlay {
  background: rgba(var(--brand-color-rgb), 0.72);
  backdrop-filter: blur(10px);
}

/* ✅ hover 半透明叠加 */
.btn-outline:hover {
  background: rgba(var(--color-button-filled-primary-rgb), 0.08);
}

三、CSS 变量使用最佳实践

3.1 品牌色场景

scss
/* ✅ 使用品牌主色 */
.selected-item {
  border-left: 3px solid var(--brand-color);
  color: var(--color-text-brand);
  background-color: var(--color-bg-brand-tint);
}

/* ✅ hover 态使用 hover 变体 */
.menu-item:hover {
  background: var(--color-bg-brand-tint-light);
  color: var(--color-text-brand-hover);
}

/* ✅ active / 选中态使用深色变体 */
.menu-item:active,
.menu-item.active {
  background: var(--color-bg-brand-shade);
  color: #ffffff;
}

3.2 按钮色场景

scss
/* ===== 填充主按钮 ===== */
.btn-primary {
  background-color: var(--color-button-filled-primary, #0564f5);
  color: var(--color-button-filled-primary-color);
  border: none;

  &:hover {
    background-color: var(--color-button-filled-primary-hover);
  }

  &:active {
    background-color: var(--color-button-filled-primary-active);
  }

  &:disabled {
    background-color: var(--color-button-filled-primary-disabled);
    color: var(--color-button-filled-primary-color-disabled);
    cursor: not-allowed;
  }
}

/* ===== 轮廓按钮(白色背景,复用填充按钮主色作为边框和文字色)===== */
.btn-outline-primary {
  background: transparent;
  color: var(--color-button-filled-primary);
  border: 2px solid var(--color-button-filled-primary);

  &:hover {
    background: rgba(var(--color-button-filled-primary-rgb), 0.08);
    border-color: var(--color-button-filled-primary-hover);
    color: var(--color-button-filled-primary-hover);
  }

  &:active {
    border-color: var(--color-button-filled-primary-active);
    color: var(--color-button-filled-primary-active);
  }
}

/* ===== 文字按钮(无边框,复用填充按钮主色作为文字色)===== */
.btn-text-primary {
  background: transparent;
  color: var(--color-button-filled-primary);
  border: 1px solid transparent;

  &:hover {
    background: rgba(var(--color-button-filled-primary-rgb), 0.08);
    color: var(--color-button-filled-primary-hover);
  }
}

/* ===== 次按钮 ===== */
.btn-secondary {
  background-color: var(--color-button-filled-primary-secondary);
  color: var(--color-button-filled-primary-color);

  &:hover {
    background-color: var(--color-button-filled-primary-secondary-hover);
  }

  &:disabled {
    background-color: var(--color-button-filled-primary-secondary-disabled);
    color: var(--color-button-filled-primary-color-disabled);
    cursor: not-allowed;
  }
}

3.3 边框元素场景

scss
/* ✅ 输入框聚焦边框 */
.input:focus,
.select-open {
  border-color: var(--color-border-brand);
  box-shadow: 0 0 0 2px rgba(var(--color-border-brand-rgb), 0.15);
}

/* ✅ 标签左边框标识 */
.tag-brand {
  border-left: 3px solid var(--brand-color);
}

/* ✅ 虚线分割 */
.divider-dashed {
  border-top: 1px dashed var(--color-border-brand-tint);
}

3.4 文本场景

scss
/* ✅ 关键数据指标 */
.stat-value {
  color: var(--color-text-brand);
  font-size: 24px;
  font-weight: 700;
}

/* ✅ 页面主标题 */
.page-title {
  color: var(--color-text-brand);
}

/* ✅ 浅底高亮文本块 */
.highlight-block {
  background: var(--color-bg-brand-tint);
  color: var(--color-text-brand);
  border-radius: 8px;
  padding: 12px 16px;
}

3.5 文字链接场景

scss
/* ✅ 文字链接 */
.text-link {
  color: var(--color-text-brand-link);

  &:hover {
    color: var(--color-text-brand-hover);
    text-decoration: underline;
  }
}

/* ✅ 带图标箭头的链接 */
.link-item {
  color: var(--color-text-brand-link);
  cursor: pointer;

  &:hover {
    color: var(--color-text-brand-hover);
    background: var(--color-bg-brand-tint);

    .link-item__arrow {
      color: var(--color-icon-brand-hover);
      transform: translateX(4px);
    }
  }

  &__arrow {
    color: var(--color-text-muted);
    transition: transform 0.2s ease;
  }
}

3.6 图标场景

scss
/* ✅ 实心图标 */
.icon-solid {
  background: var(--color-icon-brand);
  color: #ffffff;

  &:hover {
    background: var(--color-icon-brand-hover);
  }
}

/* ✅ 线框图标 */
.icon-outline {
  background: transparent;
  color: var(--color-icon-brand);
  border: 2px solid var(--color-border-brand);

  &:hover {
    color: var(--color-icon-brand-hover);
    border-color: var(--color-icon-brand-hover);
    background: rgba(var(--color-icon-brand-hover-rgb), 0.06);
  }
}

/* ✅ 可点击交互图标 */
.icon-clickable:hover {
  color: var(--color-icon-fill-brand-link);
}

3.7 半透明覆盖层场景

scss
/* ✅ 使用 -rgb 伴侣变量实现半透明 */
.overlay {
  background: rgba(var(--brand-color-rgb), 0.72);
  backdrop-filter: blur(10px);
}

.tooltip {
  background: rgba(var(--color-bg-brand-rgb), 0.9);
}

.mask {
  background: rgba(var(--brand-color-rgb), 0.5);
}

3.8 添加 fallback 默认值(推荐)

scss
/* ✅ 推荐:提供 fallback 值,确保 CSS 变量未加载时也有默认表现 */
.button {
  background-color: var(--color-button-filled-primary, #0564f5);
  border-color: var(--color-border-brand, #0564f5);
  color: var(--color-text-brand, #0564f5);
}

四、组件元素与 CSS 变量映射规范

以下表格定义了 哪些 UI 元素应该使用哪个 CSS 变量,是开发新组件的权威参考。

组件/元素类型推荐使用的 CSS 变量说明
主操作按钮背景--color-button-filled-primary主要按钮的正常态背景色
主操作按钮 Hover--color-button-filled-primary-hover鼠标悬停时的按钮背景
主操作按钮 Active--color-button-filled-primary-active点击按下时的按钮背景
主操作按钮文字--color-button-filled-primary-color主按钮上的文字颜色
主操作按钮禁用--color-button-filled-primary-disabled + --color-button-filled-primary-color-disabled禁用状态的按钮
次按钮背景--color-button-filled-primary-secondary次要按钮正常态背景
次按钮 Hover--color-button-filled-primary-secondary-hover次要按钮 hover 态
次按钮禁用--color-button-filled-primary-secondary-disabled次要按钮 disabled
轮廓按钮边框/文字--color-button-filled-primary轮廓样式按钮的边框色和文字色
文本按钮文字--color-button-filled-primary无边框的文字按钮颜色
导航栏背景--brand-nav-header-bgHeader / NavBar 组件
导航栏标题文字--brand-nav-header-font页面标题、面包屑文字
导航栏图标--brand-nav-header-icon返回按钮、关闭按钮图标
导航栏选中态高亮--brand-nav-header-select + --brand-nav-header-select-bgTab 选中项高亮
卡片/容器浅色背景--color-bg-brand-tint统计卡片背景、选中行背景
卡片/容器深色背景--color-bg-brandBanner 区域、重要信息块
输入框聚焦边框--color-border-brandInput focus / Select 打开状态
分割线/标签左边框--brand-color左侧品牌色竖线标识
品牌色图标填充--color-icon-brandSVG 图标的 fill 或 color
品牌色图标 Hover--color-icon-fill-brand-link可点击图标的 hover 交互色
主标题/关键数据文字--color-text-brand金额、统计数值
链接文字正常态--color-text-brand-link「查看详情」链接文字
链接文字 Hover 态--color-text-brand-hover链接 hover 时颜色加深
禁用态文字--color-text-brand-disabled禁用按钮上的文字

五、ECharts 等第三方库场景处理

注意:ECharts 的配置项 不支持 var(--css-variable) 写法,只接受具体颜色值(hex/rgb)。

正确做法:使用 getCssVarHex() 转换

tsx
import { getCssVarHex } from 'neo-ui-common/ThemeHelper';

const MyChart: React.FC = () => {
  // 在渲染前将 CSS 变量转换为 hex 值
  const brandColor = getCssVarHex('--brand-color');           // → '#ff6b35'
  const brandColorShade = getCssVarHex('--brand-color-shade'); // → '#cc3600'
  const brandColorTint = getCssVarHex('--color-bg-brand-tint'); // → '#fff3eb'

  const option = {
    color: [brandColor, brandColorShade],
    series: [{
      type: 'bar',
      itemStyle: {
        color: brandColor,
        borderRadius: [4, 4, 0, 0],
      },
      emphasis: {
        itemStyle: {
          color: brandColorShade,
        },
      },
    }],
    backgroundColor: brandColorTint,
  };

  return <ReactECharts option={option} />;
};

错误做法

tsx
// ❌ 错误:ECharts 无法解析 CSS 变量!
const badOption = {
  color: ['var(--brand-color)'],                // ← 显示为无色或默认色
  series: [{
    lineStyle: { color: 'var(--brand-color)' },  // ← 同样无效
  }],
};

六、完整示例

以下是一个完整的"数据概览卡片"组件,展示如何综合使用各类 CSS 变量:

tsx
// DataOverviewCard.tsx
import React from 'react';
import './DataOverviewCard.scss';

interface DataOverviewCardProps {
  title: string;
  value: string | number;
  trend?: { direction: 'up' | 'down'; percent: string };
  onClick?: () => void;
}

const DataOverviewCard: React.FC<DataOverviewCardProps> = ({
  title,
  value,
  trend,
  onClick,
}) => {
  return (
    <div className="data-overview-card" onClick={onClick}>
      <div className="data-overview-card__header">
        <span className="data-overview-card__title">{title}</span>
        <span className="data-overview-card__icon">📊</span>
      </div>
      <div className="data-overview-card__value">{value}</div>
      {trend && (
        <span className={`data-overview-card__trend data-overview-card__trend--${trend.direction}`}>
          {trend.direction === 'up' ? '↑' : '↓'} {trend.percent}
        </span>
      )}
    </div>
  );
};

export default DataOverviewCard;
scss
// DataOverviewCard.scss

.data-overview-card {
  // 背景:使用品牌浅色变体,提供轻微品牌氛围
  background: var(--color-bg-brand-tint, #e6f4ff);
  border: 1px solid var(--color-border-brand-tint, #e6f4ff);
  border-radius: 12px;
  padding: 20px;
  cursor: pointer;
  transition: all 0.3s ease;

  &:hover {
    // hover 时加深背景
    background: var(--color-bg-brand, #0564f5);
    transform: translateY(-2px);
    box-shadow: 0 4px 16px rgba(var(--color-bg-brand-rgb, 5, 100, 245), 0.25);

    // hover 时内部文字变白
    .data-overview-card__title,
    .data-overview-card__value {
      color: #ffffff;
    }
  }

  &__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }

  // 标题:品牌色文字
  &__title {
    font-size: 13px;
    font-weight: 500;
    color: var(--color-text-brand, #0564f5);
    transition: color 0.3s ease;
  }

  &__icon {
    font-size: 18px;
  }

  // 数值:品牌色 + 大号加粗
  &__value {
    font-size: 28px;
    font-weight: 700;
    color: var(--color-text-brand, #0564f5);
    margin-top: 8px;
    transition: color 0.3s ease;
  }

  // 趋势:正值绿色 / 负值红色
  &__trend {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-top: 8px;
    font-size: 12px;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: 12px;

    &--up {
      color: var(--color-success, #10b981);
      background: rgba(16, 185, 129, 0.08);
    }

    &--down {
      color: var(--color-error, #ef4444);
      background: rgba(239, 68, 68, 0.08);
    }
  }
}

七、调试验证

开发完成后,可通过浏览器控制台手动切换主题色,快速验证组件是否正确响应换肤:

js
// 切换到紫色品牌色 — 页面中品牌色元素应同步变化
NeoThemeHelper.setBaseColor("#923dda")

// 切换到橙色按钮色 — 所有按钮颜色应同步变化
NeoThemeHelper.setButtonPrimaryColor("#ff6b35")

提示主题换肤在线 Demo 提供了可视化控制面板,可交互切换品牌色、按钮色、导航色,实时预览效果。


八、检查清单

开发新组件时,请确保符合以下规范:

  • [ ] 所有品牌相关颜色必须使用 var(--brand-color-*)var(--color-*-brand-*) 系列 CSS 变量
  • [ ] 不存在硬编码的品牌色 hex/rgb 值(中性色如 #fff#000transparent 除外)
  • [ ] 禁止直接从 ColorsConfig / getNeoContext('ColorsConfig') 读取颜色值并以内联 style= 写入
  • [ ] 半透明场景使用 rgba(var(--xxx-rgb), alpha) 写法
  • [ ] 按钮颜色统一使用 --color-button-filled-primary-* 系列变量
  • [ ] 导航栏颜色统一使用 --brand-nav-header-* 系列变量
  • [ ] ECharts 等第三方库使用 getCssVarHex() 转换 CSS 变量为 hex 值
  • [ ] 建议为 CSS 变量添加 fallback 默认值(如 var(--brand-color, #0564f5)