Skip to content

主题

NetNexus UI 的主题由运行时主题管理器、<html> 上的 data-* 属性以及 --nn-* CSS 变量共同组成。

基本接入

完整样式已经包含主题 Token:

js
import { createApp } from 'vue';
import NetNexusUi, { initializeTheme } from 'netnexus-ui';
import 'netnexus-ui/style.css';
import App from './App.vue';

initializeTheme();

createApp(App).use(NetNexusUi).mount('#app');

只使用主题系统时,可以改为:

js
import { initializeTheme } from 'netnexus-ui/theme';
import 'netnexus-ui/theme.css';

initializeTheme();

内置 preset

常量解析后的明暗模式说明
APP_THEME_PRESET.BLUEbluelight默认 preset
APP_THEME_PRESET.ORANGEorangelight橙色品牌色
APP_THEME_PRESET.DARKdarkdark深色主题

相关常量:

js
import {
    APP_THEME,
    APP_THEME_PRESET,
    APP_THEME_PRESET_OPTIONS,
    DEFAULT_THEME_PRESET,
    THEME_PRESET_STORAGE_KEY
} from 'netnexus-ui/theme';
  • DEFAULT_THEME_PRESETblue
  • THEME_PRESET_STORAGE_KEYnetnexus.themePreset
  • APP_THEME_PRESET_OPTIONS 提供“蓝色”“橙色”“深色”三个可直接渲染的选项。
  • APP_THEME 包含 lightdark

初始化后,管理器会维护以下 DOM 契约:

html
<html data-theme="light" data-theme-preset="blue" style="color-scheme: light"></html>

深色 preset 对应:

html
<html data-theme="dark" data-theme-preset="dark" style="color-scheme: dark"></html>

切换主题

默认管理器通过根入口和 netnexus-ui/theme 子入口同时导出。

vue
<script setup>
    import { APP_THEME_PRESET_OPTIONS, getThemeState, setThemePreset } from 'netnexus-ui/theme';

    const { themePreset, resolvedTheme } = getThemeState();
</script>

<template>
    <section>
        <p>preset:{{ themePreset }};模式:{{ resolvedTheme }}</p>

        <NnSpace>
            <NnButton
                v-for="option in APP_THEME_PRESET_OPTIONS"
                :key="option.value"
                :type="themePreset === option.value ? 'primary' : 'default'"
                @click="setThemePreset(option.value)"
            >
                {{ option.label }}
            </NnButton>
        </NnSpace>
    </section>
</template>

setThemePreset() 默认把选择写入 localStorage。只应用一次、不覆盖本地选择时:

js
setThemePreset(APP_THEME_PRESET.DARK, { persistLocal: false });

默认管理器 API:

API行为
initializeTheme()从存储读取 preset、应用 DOM 属性并开始监听;返回停止监听的函数
setThemePreset(preset, options?)规范化并应用内置 preset;默认持久化
getThemeState()返回只读的 themePreset Ref 和 resolvedTheme ComputedRef
disposeTheme()停止默认管理器的 Vue watcher
isAppThemePreset(value)判断值是否为三个内置 preset 之一
normalizeThemePreset(value, fallback?)非法值回退到 fallback,默认回退到 blue
getResolvedThemeFromPreset(preset)dark 返回 dark,其他内置值返回 light

disposeTheme() 只停止 watcher,不会删除 DOM 属性,也不会清除 localStorage

独立主题管理器

需要自定义存储键、preset 或挂载目标时,使用 createThemeManager(),不要复用只接受内置 preset 的 setThemePreset()

js
import { createThemeManager } from 'netnexus-ui/theme';

const brandTheme = createThemeManager({
    presets: ['brand', 'night'],
    defaultPreset: 'brand',
    storageKey: 'my-app.theme',
    resolveTheme: preset => (preset === 'night' ? 'dark' : 'light')
});

const stop = brandTheme.initialize();

brandTheme.setPreset('night');

// 保存 stop,并在应用真正不再使用该管理器时调用。

配套 CSS:

css
html[data-theme-preset='brand'] {
    --nn-color-primary: #7c3aed;
    --nn-color-primary-hover: #8b5cf6;
    --nn-color-primary-active: #6d28d9;
    --nn-color-link: #7c3aed;
    --nn-color-text-info: #6d28d9;
    --nn-color-border-info: #a78bfa;
}

html[data-theme-preset='night'] {
    --nn-color-primary: #c4b5fd;
    --nn-color-primary-hover: #ddd6fe;
    --nn-color-primary-active: #a78bfa;
}

未覆盖的变量会继续使用基础亮色或深色值。要获得完整的品牌主题,应覆盖所有会影响目标界面的语义 Token,而不是依赖某个组件的内部选择器。

创建选项

选项默认值说明
presets三个内置 preset允许使用的字符串数组
defaultPresetblue,或允许列表的第一项存储为空或值非法时的回退值
storageKeynetnexus.themePreset持久化键名
storage浏览器 localStorage存储对象、返回存储对象的函数或 null
targetdocument.documentElement具有 dataset 的对象、返回该对象的函数或 null
resolveTheme内置 preset 解析函数把 preset 映射为明暗模式;供内置 CSS 使用时应返回 lightdark

实例 API

属性或方法说明
themePreset当前 preset 的只读 Ref
resolvedTheme解析后模式的只读 ComputedRef
initialize()读取存储、应用主题、启动 watcher,并返回 dispose
setPreset(preset, options?)切换主题;persistLocal: false 可跳过写入
apply()立即把当前状态重新应用到 target
dispose()停止 watcher

自定义存储对象至少需要 getItem(key)setItem(key, value)。读写失败时当前实现会输出警告并继续使用内存中的状态。

CSS Token

src/styles/theme.css 当前定义 110 个不同的 --nn-* 变量。值会随 preset 变化,以下按用途列出公开名称;实际值和选择器顺序以源码为准。

字体与基础语义色

text
--nn-font-family
--nn-color-primary
--nn-color-primary-hover
--nn-color-primary-active
--nn-color-link
--nn-color-success
--nn-color-warning
--nn-color-error
--nn-color-info

背景色

text
--nn-color-bg-body
--nn-color-bg-layout
--nn-color-bg-surface
--nn-color-bg-elevated
--nn-color-bg-muted
--nn-color-bg-subtle
--nn-color-bg-hover
--nn-color-bg-disabled
--nn-color-bg-code
--nn-color-bg-selected
--nn-color-bg-info-subtle
--nn-color-bg-success-subtle
--nn-color-bg-warning-subtle
--nn-color-bg-stale
--nn-color-bg-danger-subtle
--nn-color-bg-progress
--nn-color-bg-console
--nn-color-bg-console-muted
--nn-color-bg-console-success
--nn-color-bg-console-info
--nn-color-bg-console-error
--nn-color-bg-sider
--nn-color-bg-sider-hover
--nn-color-bg-sider-active
--nn-color-bg-card-head
--nn-color-bg-card-head-hover
--nn-color-bg-card-head-control
--nn-color-bg-card-head-control-hover
--nn-color-bg-card-head-ghost
--nn-color-bg-card-head-ghost-hover
--nn-color-overlay-mask

文本色

text
--nn-color-text
--nn-color-text-strong
--nn-color-text-secondary
--nn-color-text-muted
--nn-color-text-placeholder
--nn-color-text-disabled
--nn-color-text-inverse
--nn-color-text-sider
--nn-color-text-info
--nn-color-text-success
--nn-color-text-warning
--nn-color-text-stale
--nn-color-text-card-head-control
--nn-color-text-card-head-ghost
--nn-color-text-console
--nn-color-text-console-muted
--nn-color-text-console-label
--nn-color-text-console-success
--nn-color-text-console-success-strong
--nn-color-text-console-info
--nn-color-text-console-info-strong
--nn-color-text-console-error
--nn-color-text-console-error-strong

边框与 Tooltip

text
--nn-color-border
--nn-color-border-light
--nn-color-border-info
--nn-color-border-danger
--nn-color-border-sider
--nn-color-border-sider-active
--nn-color-border-card-head-control
--nn-color-border-card-head-ghost
--nn-color-tooltip-bg
--nn-color-tooltip-text

语法高亮

text
--nn-color-syntax-tag
--nn-color-syntax-attribute
--nn-color-syntax-value
--nn-color-syntax-comment
--nn-color-syntax-declaration
--nn-color-syntax-cdata
--nn-color-syntax-entity
--nn-color-syntax-punctuation

终端配色

终端 token 独立于通用 Console token,包含背景、前景、光标、选区和完整 ANSI 16 色;这样浅色 preset 可以提供浅色终端,而日志 Console 仍可保持深色外观。

text
--nn-color-terminal-background
--nn-color-terminal-foreground
--nn-color-terminal-cursor
--nn-color-terminal-selection-background
--nn-color-terminal-ansi-black
--nn-color-terminal-ansi-red
--nn-color-terminal-ansi-green
--nn-color-terminal-ansi-yellow
--nn-color-terminal-ansi-blue
--nn-color-terminal-ansi-magenta
--nn-color-terminal-ansi-cyan
--nn-color-terminal-ansi-white
--nn-color-terminal-ansi-bright-black
--nn-color-terminal-ansi-bright-red
--nn-color-terminal-ansi-bright-green
--nn-color-terminal-ansi-bright-yellow
--nn-color-terminal-ansi-bright-blue
--nn-color-terminal-ansi-bright-magenta
--nn-color-terminal-ansi-bright-cyan
--nn-color-terminal-ansi-bright-white

扁平化兼容令牌

以下名称为兼容既有主题而保留。内置主题中阴影与旧焦点光晕均为 none,两个 gradient 名称解析为纯色;新主题不应重新引入悬浮阴影或渐变。

text
--nn-shadow-sider
--nn-shadow-elevated
--nn-shadow-floating
--nn-shadow-floating-hover
--nn-shadow-card-head-control
--nn-focus-shadow-error
--nn-focus-shadow-primary
--nn-gradient-panel
--nn-gradient-progress

已知边界

  • 默认管理器只接受 blueorangedark;自定义 preset 必须使用独立管理器。
  • 管理器没有监听浏览器的 storage 事件,不会自动同步其他标签页中的主题变化。
  • 服务端没有 windowdocument 时,默认 storage 和 target 都是 null,调用不会写 DOM;客户端挂载时仍需再次初始化。
  • 保存过的非默认主题要等 JavaScript 初始化后才会写入 DOM。对首屏颜色切换敏感的应用应尽早调用 initializeTheme()
  • dispose() 不清理属性或存储;如需恢复,应先切换到目标 preset,再停止监听。
  • 自定义 resolveTheme 的返回值会直接写入 data-themestyle.colorScheme。使用内置 CSS 时应返回有效的 lightdark

相关源码

Released under the MIT License.