一站式搞定品牌风格:TinyRobot 主题定制从入门到精通

一站式搞定品牌风格:TinyRobot 主题定制从入门到精通

每个品牌都有自己的视觉风格------颜色、圆角、间距、字号,这些看似琐碎的细节,共同构成了用户对产品的第一印象。TinyRobot 的主题系统正是为此而生:通过 CSS 变量 + ThemeProvider + useTheme 的三层架构,开发者可以一站式搞定从全局主题到单组件微调的全部定制需求。

本文将从核心概念出发,逐步深入到高级用法和实战案例,带你从入门到精通 TinyRobot 的主题定制。

主题系统核心概念

TinyRobot 的主题系统基于 CSS 变量实现,核心组件是 ThemeProvideruseTheme 组合式函数。

两个核心概念

  1. 主题(Theme):定义应用的整体视觉风格,如品牌色、圆角、间距等。一个主题可以包含多组 CSS 变量值,覆盖亮色和暗色两种颜色模式。
  2. 颜色模式(Color Mode) :控制亮色/暗色模式,支持 lightdarkauto(跟随系统)三种模式。

主题和颜色模式是独立的概念------你可以在同一个主题下切换亮色/暗色模式,也可以在不同主题下使用相同的颜色模式。

typescript 复制代码
// 颜色模式类型
type ColorMode = 'light' | 'dark' | 'auto'
  • light:亮色模式
  • dark:暗色模式
  • auto:自动模式,跟随系统 prefers-color-scheme 设置

ThemeProvider 基础用法

包裹应用

使用 ThemeProvider 包裹你的应用,它会自动在目标元素上添加 data-tr-themedata-tr-color-mode 属性:

vue 复制代码
<template>
  <ThemeProvider>
    <YourApp />
  </ThemeProvider>
</template>

<script setup>
import { ThemeProvider } from '@opentiny/tiny-robot'
</script>

包裹后,html 元素(默认 targetElement)会被添加:

html 复制代码
<html data-tr-theme="" data-tr-color-mode="light">
  <!-- 或 auto 模式下跟随系统 -->
  <html data-tr-theme="" data-tr-color-mode="dark">
</html>

ThemeProvider Props

属性 类型 必填 默认值 说明
colorMode ColorMode 'auto' 颜色模式,支持 v-model 双向绑定
targetElement string 'html' 应用主题属性选择器的目标元素
theme string '' 主题名称,支持 v-model 双向绑定
storage ThemeStorage - 主题数据存储实现,用于持久化
storageKey string 'tiny-robot-theme-data' 存储键名

CSS 属性选择器覆写

ThemeProvider 设置的 data-tr-themedata-tr-color-mode 属性,是 CSS 变量覆写的核心锚点。开发者使用属性选择器来定义不同主题和颜色模式下的样式:

按颜色模式覆写

css 复制代码
/* 亮色模式 */
[data-tr-color-mode='light'] {
  --tr-primary-color: #5e7ce0;
  --tr-background-color: #ffffff;
  --tr-sender-bg-color: #ffffff;
  --tr-sender-text-color: #333333;
}

/* 暗色模式 */
[data-tr-color-mode='dark'] {
  --tr-primary-color: #7693f5;
  --tr-background-color: #1a1a1a;
  --tr-sender-bg-color: #1e1e2e;
  --tr-sender-text-color: #cdd6f4;
}

按主题名称覆写

css 复制代码
/* 默认主题(theme="") */
[data-tr-theme=''] {
  --tr-border-radius: 8px;
}

/* 自定义主题(theme="brand") */
[data-tr-theme='brand'] {
  --tr-border-radius: 16px;
  --tr-sender-bg-color: #f0f7ff;
  --tr-sender-border-radius: 12px;
}

组合选择器

css 复制代码
/* brand 主题 + 亮色模式 */
[data-tr-theme='brand'][data-tr-color-mode='light'] {
  --tr-sender-bg-color: #f0f7ff;
  --tr-container-bg-color: #ebf5ff;
}

/* brand 主题 + 暗色模式 */
[data-tr-theme='brand'][data-tr-color-mode='dark'] {
  --tr-sender-bg-color: #1a365d;
  --tr-container-bg-color: #0d1b3e;
}

主题切换

通过 Props 控制

ThemeProvider 的 themecolorMode 属性支持 v-model 双向绑定:

vue 复制代码
<template>
  <ThemeProvider
    v-model:theme="currentTheme"
    v-model:color-mode="colorMode"
  >
    <YourApp />
  </ThemeProvider>
</template>

<script setup>
import { ref } from 'vue'

const currentTheme = ref('')     // 默认主题
const colorMode = ref('auto')    // 自动模式
</script>

通过 useTheme 控制

在 ThemeProvider 包裹的组件中使用 useTheme 组合式函数,可以通过代码动态切换主题:

vue 复制代码
<script setup>
import { useTheme } from '@opentiny/tiny-robot'

const {
  theme,              // 当前主题名称(Ref<string>)
  colorMode,           // 当前颜色模式(Ref<ColorMode>)
  resolvedColorMode,   // 解析后的颜色模式(Readonly<Ref<'light' | 'dark'>>)
  systemColorMode,     // 系统颜色模式(Readonly<Ref<'light' | 'dark'>>)
  setTheme,            // 设置主题
  toggleColorMode,     // 切换亮暗模式
  setColorMode         // 设置颜色模式
} = useTheme()

// 设置主题
setTheme('brand')

// 设置颜色模式
setColorMode('dark')

// 切换亮暗模式
toggleColorMode()
</script>

useTheme 返回值详解:

属性 类型 说明
theme Ref<string> 当前主题名称
colorMode Ref<ColorMode> 当前颜色模式
resolvedColorMode `Readonly<Ref<'light' 'dark'>>`
systemColorMode `Readonly<Ref<'light' 'dark'>>`
setTheme (newTheme: string) => boolean 设置主题,返回是否成功
toggleColorMode () => boolean 切换亮暗模式
setColorMode (mode: ColorMode) => boolean 设置颜色模式

重要提示useTheme 只能在 ThemeProvider 包裹的组件中使用。如果在没有 ThemeProvider 的组件中使用,相关方法会返回 false 并在控制台输出警告信息。

实战:主题切换面板

vue 复制代码
<template>
  <ThemeProvider v-model:theme="currentTheme" v-model:color-mode="colorMode">
    <div class="chat-app">
      <!-- 主题切换按钮 -->
      <div class="theme-controls">
        <button @click="toggleColorMode()">
          {{ resolvedColorMode === 'dark' ? '☀️' : '🌙' }}
        </button>
        <select v-model="currentTheme">
          <option value="">默认</option>
          <option value="brand">品牌</option>
          <option value="minimal">极简</option>
        </select>
      </div>

      <tr-container title="AI 助手">
        <tr-bubble-list :messages="messages" />
        <template #footer>
          <tr-sender v-model="inputText" @submit="handleSubmit" />
        </template>
      </tr-container>
    </div>
  </ThemeProvider>
</template>

<script setup>
import { ref, computed } from 'vue'
import { ThemeProvider, useTheme } from '@opentiny/tiny-robot'

const currentTheme = ref('')
const colorMode = ref('auto')
const { resolvedColorMode, toggleColorMode } = useTheme()
</script>

嵌套主题

ThemeProvider 可以嵌套使用,子组件会往上查找最近的 ThemeProvider:

vue 复制代码
<template>
  <ThemeProvider theme="default" color-mode="light">
    <div>外层:默认主题(亮色)</div>

    <!-- 主题预览区 -->
    <ThemeProvider theme="brand" color-mode="dark">
      <div>内层:品牌主题(暗色)</div>
      <!-- 这里的 Sender 使用 brand + dark 主题 -->
      <tr-sender />
    </ThemeProvider>
  </ThemeProvider>
</template>

嵌套主题的适用场景:

  1. 特定区域使用不同主题:如左侧导航使用品牌主题,右侧内容使用极简主题
  2. 主题预览功能:在一个页面中同时展示多个主题的效果
  3. 组件级主题隔离:某些组件使用独立主题,不受全局主题影响

嵌套的 ThemeProvider 会在其对应的 targetElement 上设置独立的属性,子组件自动匹配最近的 ThemeProvider。

主题持久化

使用 storagestorageKey 属性持久化主题设置,用户切换主题后,刷新页面仍能恢复之前的选择:

typescript 复制代码
// ThemeStorage 接口
type ThemeStorage = Pick<Storage, 'getItem' | 'setItem'>

localStorage 持久化

vue 复制代码
<template>
  <ThemeProvider
    v-model:theme="currentTheme"
    v-model:color-mode="colorMode"
    :storage="localStorage"
    storage-key="my-app-theme"
  >
    <YourApp />
  </ThemeProvider>
</template>

sessionStorage 持久化

vue 复制代码
<template>
  <ThemeProvider
    v-model:theme="currentTheme"
    v-model:color-mode="colorMode"
    :storage="sessionStorage"
    storage-key="my-app-theme"
  >
    <YourApp />
  </ThemeProvider>
</template>

自定义存储实现

可以实现自定义存储策略,如将主题数据保存到远程服务器:

typescript 复制代码
const customStorage: ThemeStorage = {
  getItem: (key: string) => {
    // 从远程服务器获取
    return serverFetchThemeData(key)
  },
  setItem: (key: string, value: string) => {
    // 保存到远程服务器
    serverSaveThemeData(key, value)
  }
}
vue 复制代码
<ThemeProvider :storage="customStorage" storage-key="user-theme-preference">
  <YourApp />
</ThemeProvider>

自定义目标元素:targetElement

默认情况下,ThemeProvider 在 html 元素上设置属性,影响整个页面。通过 targetElement 可以指定主题应用的根元素:

vue 复制代码
<template>
  <div id="app-container">
    <ThemeProvider target-element="#app-container">
      <YourApp />
    </ThemeProvider>
  </div>
</template>

此时,data-tr-themedata-tr-color-mode 属性会被设置在 #app-container 元素上,主题只会影响该元素及其子元素。

适用场景:

  • 页面中只有部分区域使用 TinyRobot 组件
  • 多个独立的聊天窗口需要不同主题
  • 避免主题属性污染全局 html 元素

从 v0.3 迁移:theme prop → ThemeProvider

v0.3 的 Sender 使用 theme prop 设置主题:

vue 复制代码
<!-- v0.3 -->
<tr-sender theme="dark" />

v0.4 移除了 theme prop,改用 ThemeProvider 包裹:

vue 复制代码
<!-- v0.4 -->
<ThemeProvider color-mode="dark">
  <tr-sender />
</ThemeProvider>

迁移优势:

  • 全局生效:一个 ThemeProvider 包裹所有组件,无需每个组件单独设置
  • 自动继承:子组件自动继承父级 ThemeProvider 的配置
  • 双向绑定:v-model:color-mode 支持动态切换
  • 持久化:内置 storage 支持,无需手动保存主题偏好

Sender 主题定制实战

结合 CSS 变量和 ThemeProvider,定制 Sender 的完整外观:

实战1:品牌主题 Sender

css 复制代码
/* brand 主题变量定义 */
[data-tr-theme='brand'] {
  --tr-sender-bg-color: #f0f7ff;
  --tr-sender-text-color: #1a365d;
  --tr-sender-placeholder-color: #a0aec0;
  --tr-sender-button-hover-bg: #bee3f8;
  --tr-sender-border-radius: 16px;
  --tr-sender-padding: 14px 16px;
  --tr-sender-gap: 10px;
  --tr-sender-footer-gap: 10px;
  --tr-sender-footer-padding: 10px 16px;
  --tr-sender-button-size: 36px;
  --tr-sender-button-size-submit: 44px;

  --tr-container-bg-color: #ebf5ff;
  --tr-container-border-color: #bee3f8;

  --tr-prompt-bg: #bee3f8;
  --tr-prompt-bg-hover: #90cdf4;
  --tr-prompt-border-radius: 12px;
  --tr-prompt-title-color: #1a365d;
}
vue 复制代码
<template>
  <ThemeProvider v-model:theme="currentTheme">
    <tr-container title="AI 助手">
      <tr-bubble-list :messages="messages" />
      <template #footer>
        <tr-sender @submit="handleSubmit">
          <template #footer="{ disabled, loading }">
            <tr-voice-button :disabled="disabled || loading" />
          </template>
        </tr-sender>
      </template>
    </tr-container>
  </ThemeProvider>
</template>

<script setup>
import { ref } from 'vue'
const currentTheme = ref('brand')
</script>

实战2:亮暗自适应 Sender

css 复制代码
/* 亮色模式 */
[data-tr-color-mode='light'] {
  --tr-sender-bg-color: #ffffff;
  --tr-sender-text-color: #333333;
  --tr-sender-placeholder-color: #999999;
  --tr-sender-button-hover-bg: #f5f5f5;
  --tr-bubble-box-bg: #f8f9fa;
}

/* 暗色模式 */
[data-tr-color-mode='dark'] {
  --tr-sender-bg-color: #1e1e2e;
  --tr-sender-text-color: #cdd6f4;
  --tr-sender-placeholder-color: #585b70;
  --tr-sender-button-hover-bg: #313244;
  --tr-bubble-box-bg: #313244;
}
vue 复制代码
<template>
  <!-- 使用 auto 模式,自动跟随系统 -->
  <ThemeProvider v-model:color-mode="colorMode" :storage="localStorage">
    <div class="theme-toggle">
      <button @click="toggleColorMode()">
        {{ resolvedColorMode === 'dark' ? '切换到亮色' : '切换到暗色' }}
      </button>
    </div>
    <tr-sender @submit="handleSubmit" />
  </ThemeProvider>
</template>

<script setup>
import { ref } from 'vue'
import { ThemeProvider, useTheme } from '@opentiny/tiny-robot'

const colorMode = ref('auto')
const { resolvedColorMode, toggleColorMode } = useTheme()
</script>

实战3:多主题切换面板

css 复制代码
/* 默认主题 */
[data-tr-theme=''] {
  --tr-sender-bg-color: #ffffff;
  --tr-sender-border-radius: 8px;
  --tr-container-border-color: #e5e7eb;
}

/* 品牌主题 */
[data-tr-theme='brand'] {
  --tr-sender-bg-color: #f0f7ff;
  --tr-sender-border-radius: 16px;
  --tr-container-border-color: #bee3f8;
  --tr-container-bg-color: #ebf5ff;
}

/* 极简主题 */
[data-tr-theme='minimal'] {
  --tr-sender-bg-color: #fafafa;
  --tr-sender-border-radius: 4px;
  --tr-container-border-color: #e0e0e0;
}
vue 复制代码
<template>
  <ThemeProvider v-model:theme="currentTheme" v-model:color-mode="colorMode">
    <div class="theme-panel">
      <div class="theme-options">
        <button @click="currentTheme = ''">默认</button>
        <button @click="currentTheme = 'brand'">品牌</button>
        <button @click="currentTheme = 'minimal'">极简</button>
      </div>
      <div class="color-mode-options">
        <button @click="colorMode = 'light'">亮色</button>
        <button @click="colorMode = 'dark'">暗色</button>
        <button @click="colorMode = 'auto'">自动</button>
      </div>
    </div>

    <tr-container title="AI 助手">
      <tr-bubble-list :messages="messages" />
      <template #footer>
        <tr-sender @submit="handleSubmit" />
      </template>
    </tr-container>
  </ThemeProvider>
</template>

<script setup>
import { ref } from 'vue'

const currentTheme = ref('')
const colorMode = ref('auto')
</script>

最佳实践

1. 使用语义化 CSS 变量名

不要只覆写"视觉层"的变量,而要考虑变量的语义含义:

css 复制代码
/* 不推荐------只关注视觉 */
[data-tr-color-mode='dark'] {
  --tr-sender-bg-color: #1e1e2e;
}

/* 推荐------语义化命名 */
[data-tr-color-mode='dark'] {
  --tr-sender-bg-color: var(--app-surface-color, #1e1e2e);
}

2. 为所有 CSS 变量提供默认值

确保未自定义时也有良好显示,使用 var() 的 fallback 值:

css 复制代码
.my-sender {
  --tr-sender-bg-color: var(--my-brand-bg, #f0f7ff);
}

3. 启用主题持久化

用户切换主题后,刷新页面应该恢复之前的选择:

vue 复制代码
<ThemeProvider :storage="localStorage" storage-key="my-app-theme">
  <YourApp />
</ThemeProvider>

4. 使用 auto 模式

默认使用 auto 颜色模式,自动适配用户的系统设置。大多数用户期望应用跟随系统的亮暗偏好:

vue 复制代码
<ThemeProvider v-model:color-mode="colorMode">
  <!-- 默认 auto,但允许用户手动切换 -->
</ThemeProvider>

<script setup>
const colorMode = ref('auto')
</script>

5. 避免复杂的 CSS 计算

CSS 变量中避免使用 calc() 等复杂计算,保持主题切换流畅:

css 复制代码
/* 不推荐 */
[data-tr-theme='brand'] {
  --tr-sender-padding: calc(var(--base-padding) * 1.5);
}

/* 推荐 */
[data-tr-theme='brand'] {
  --tr-sender-padding: 18px;
}

6. 集中管理主题变量

将所有主题变量定义集中在一个或少数几个 CSS 文件中,便于维护:

css 复制代码
/* themes/light.css */
[data-tr-color-mode='light'] {
  /* 全局变量 */
  --tr-primary-color: #5e7ce0;

  /* Sender 变量 */
  --tr-sender-bg-color: #ffffff;
  --tr-sender-text-color: #333333;

  /* Container 变量 */
  --tr-container-bg-color: #ffffff;
  --tr-container-border-color: #e5e7eb;

  /* Bubble 变量 */
  --tr-bubble-box-bg: #f8f9fa;
  --tr-bubble-text-color: #333333;
}

/* themes/dark.css */
[data-tr-color-mode='dark'] {
  --tr-primary-color: #7693f5;
  --tr-sender-bg-color: #1e1e2e;
  --tr-sender-text-color: #cdd6f4;
  --tr-container-bg-color: #181825;
  --tr-bubble-box-bg: #313244;
  --tr-bubble-text-color: #cdd6f4;
}

小结

TinyRobot 的主题系统三层架构各司其职:

层级 机制 职责
CSS 变量 --tr-* 变量 定义具体的视觉值
ThemeProvider data-tr-* 属性 选择变量集合(主题 + 颜色模式)
useTheme 组合式函数 动态切换主题和颜色模式

这种三层架构的优势:

  1. 一致性:所有组件使用相同的变量前缀和命名规范
  2. 灵活性:CSS 变量可以精确到单个组件的某个属性
  3. 可组合:ThemeProvider 嵌套、持久化、自定义目标元素等高级功能
  4. 零运行时开销:CSS 变量的切换由浏览器原生处理,不涉及 JavaScript 计算

从快速入门(一个 ThemeProvider 包裹应用),到高级定制(嵌套主题 + 持久化 + 自定义存储),TinyRobot 的主题系统覆盖了从简单到复杂的全部场景。记住一句话:定义变量,选择集合,动态切换------这就是 TinyRobot 主题定制的全部秘密


🔗 TinyRobot 官网tiny-robot.opentiny.design

🔗 GitHub 仓库github.com/opentiny/ti...

相关推荐
尽欢i3 小时前
Vue3 customRef 封神教程:防抖、本地存储、自动埋点一套搞定,模板干干净净
前端·javascript·vue.js
因_崔斯汀3 小时前
Vue 模板编译:HTML 是怎么变成 JS 的?
前端·vue.js
英勇无比的消炎药4 小时前
样式随心定制:TinyRobot 样式覆写与 CSS 变量实战解析
vue.js
疯狂的魔鬼4 小时前
多角色督办任务详情页:从权限矩阵到组件拆分的完整实现
前端·vue.js·架构
英勇无比的消炎药4 小时前
拆解内核:深入分析 TinyRobot 输入区组件设计与实现原理
vue.js
英勇无比的消炎药4 小时前
组件联动进阶:玩转 TinyRobot 组合开发提升项目灵活性
vue.js
Cc_Debugger4 小时前
开发环境使用https配置
javascript·vue.js·https
触底反弹4 小时前
🎨 通义万相实战:用 Qwen 多模态 API 实现 AI 换装换姿势,10 行代码搞定!
vue.js·人工智能
零瓶水Herwt5 小时前
代替vue-currency-input使用原生货币符号
前端·vue.js