How Vue-Element-Admin Implements Dynamic Color Switching and SCSS Variable Theming

Vue-Element-Admin enables runtime theme switching by generating color clusters from SCSS variables and dynamically rewriting Element-UI's CSS in the browser without page reloads.

PanJiaChen/vue-element-admin provides a sophisticated theme system that allows users to switch primary colors dynamically while maintaining Element-UI component consistency. The implementation combines SCSS variable preprocessing with runtime CSS manipulation to deliver instant visual updates across the entire application.

How Dynamic Color Switching Works at Runtime

The theme system operates through a two-layer architecture: Vuex state management for persistence and a runtime CSS generator for instantaneous visual updates.

The ThemePicker Component Architecture

The core logic resides in src/components/ThemePicker/index.vue. This component renders an <el-color-picker> bound to a theme data property. When a user selects a new color, a watcher triggers the dynamic update chain:

watch: {
  async theme(val) {
    const oldVal = this.theme
    if (typeof val !== 'string') return
    const themeCluster = this.getThemeCluster(val.replace('#', ''))
    const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
    // CSS replacement logic follows
  }
}

Generating the Color Cluster

The getThemeCluster method in src/components/ThemePicker/index.vue generates a palette of related colors from the base primary color. It produces the base color plus ten tints (lighter variations) and one shade (darker variation):

getThemeCluster(theme) {
  const tintColor = (color, tint) => { /* color mixing logic */ }
  const shadeColor = (color, shade) => { /* shading logic */ }
  const clusters = [theme]
  for (let i = 0; i <= 9; i++) {
    clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
  }
  clusters.push(shadeColor(theme, 0.1))
  return clusters
}

Runtime CSS Injection and Replacement

The updateStyle method performs the actual CSS transformation. It fetches the original Element-UI theme-chalk stylesheet from unpkg if not already cached, then uses regular expressions to replace every occurrence of the old color cluster with the new cluster:

updateStyle(style, oldCluster, newCluster) {
  let newStyle = style
  oldCluster.forEach((color, index) => {
    newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
  })
  return newStyle
}

The transformed CSS is then written back to the DOM via a <style> tag, immediately updating all Element-UI components without requiring a page reload.

SCSS Variables That Control Element-UI Styling

The static theming foundation is defined in src/styles/element-variables.scss, which overrides Element-UI's default SCSS variables before compilation.

Primary Color Configuration in element-variables.scss

The most critical variable is $--color-primary, which sets the base brand color that the runtime system later manipulates:

/* theme colour */
$--color-primary: #1890ff;   // primary colour (used for buttons, tabs, etc.)
$--color-success: #13ce66;   // success state colour
$--color-warning: #ffba00;   // warning state colour
$--color-danger:  #ff4949;   // error / danger colour

Additional variables control typography, border colors, and table styling, but the primary color drives the dynamic theming capability.

Exporting SCSS Variables to JavaScript

To synchronize the SCSS-defined theme with the JavaScript runtime, the file uses the :export pseudo-selector, which webpack's sass-loader converts into a JavaScript module:

:export {
  theme: $--color-primary;
}

This export is consumed by src/store/modules/settings.js to initialize the default theme state:

import variables from '@/styles/element-variables.scss'

const state = {
  theme: variables.theme
}

Integrating the Theme System with Vuex

The Vuex store in src/store/modules/settings.js persists the user's theme preference across sessions. It imports the default theme from the SCSS export and provides mutations to update the value:

const state = {
  theme: variables.theme,
  showSettings: true,
  // ... other settings
}

const mutations = {
  CHANGE_SETTING: (state, { key, value }) => {
    if (state.hasOwnProperty(key)) {
      state[key] = value
    }
  }
}

The ThemePicker component emits change events that dispatch these mutations, ensuring the selected color survives page refreshes.

Toggling Between Light and Custom Dark Themes

Beyond the primary color picker, the system supports a complete theme swap via the custom-theme class. The view in src/views/theme/index.vue provides a switch that toggles this class on the document body:

import { toggleClass } from '@/utils'

export default {
  watch: {
    theme() {
      toggleClass(document.body, 'custom-theme')
    }
  }
}

The toggleClass helper in src/utils/index.js adds or removes the class, while the pre-compiled dark theme CSS in src/assets/custom-theme/index.css provides the alternative styling rules.

Summary

  • Runtime CSS Generation: The ThemePicker component in src/components/ThemePicker/index.vue generates color clusters and rewrites Element-UI's CSS in real-time using updateStyle and getThemeCluster.
  • SCSS Variable Control: The file src/styles/element-variables.scss defines $--color-primary and other brand colors, exporting the primary value to JavaScript via :export for Vuex initialization.
  • State Persistence: The Vuex settings module stores the active theme, ensuring user preferences persist across sessions without requiring localStorage manipulation in components.
  • Binary Theme Toggle: The custom-theme class mechanism in src/views/theme/index.vue allows switching to a pre-compiled dark theme via toggleClass from src/utils/index.js.

Frequently Asked Questions

How does the theme system generate color variations automatically?

The system uses the getThemeCluster method in src/components/ThemePicker/index.vue to programmatically generate ten tint variations (lighter versions) and one shade variation (darker version) from the base primary color. These are calculated using color mixing algorithms that blend the base color with white (for tints) or black (for shades) at specific ratios, creating a full palette that matches Element-UI's expected color structure.

What is the purpose of the :export block in element-variables.scss?

The :export pseudo-selector allows SCSS variables to be accessed from JavaScript code. When webpack processes src/styles/element-variables.scss, the sass-loader converts the :export { theme: $--color-primary; } declaration into a JavaScript object. This enables src/store/modules/settings.js to import the default theme value directly from the stylesheet, ensuring the Vuex state initializes with the same color defined in SCSS without hardcoding values in JavaScript.

How can I set a permanent default theme color instead of using the runtime picker?

To set a fixed default color, modify src/styles/element-variables.scss and change the $--color-primary variable to your desired hex code (for example, $--color-primary: #ff5722;). Because this file exports the theme value to the Vuex store via :export, the application will initialize with your specified color. If you want to disable the runtime picker entirely, remove the <theme-picker> component from your layout components such as src/layout/components/Navbar.vue or src/views/theme/index.vue.

What is the difference between the runtime theme picker and the custom-theme class toggle?

The runtime theme picker (implemented in src/components/ThemePicker/index.vue) performs client-side CSS manipulation to change the primary color palette in real-time without reloading, allowing any hex color to be applied instantly. In contrast, the custom-theme class toggle (used in src/views/theme/index.vue) is a binary switch that adds or removes the custom-theme CSS class from the document body, which activates a pre-compiled dark theme stylesheet (src/assets/custom-theme/index.css). The runtime picker offers granular color control, while the class toggle provides a complete pre-built theme swap.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →