Frontend Development 13 min read

Implementing One‑Click Theme Switching with SCSS in a Vue Project

This article explains how to build a one‑click skin‑changing system for a Vue application by defining SCSS variables, creating theme maps, configuring webpack and vue.config.js to inject global styles, and using both CSS custom properties and advanced SCSS features such as loops, maps, and mixins to dynamically switch colors, fonts, and sizes.

政采云技术
政采云技术
政采云技术
Implementing One‑Click Theme Switching with SCSS in a Vue Project

During front‑end development it is common to need dynamic changes such as swapping site theme colors, adjusting font sizes, or updating banner icons without rewriting CSS; the article shows how to meet these requirements by establishing a complete SCSS variable system and using it throughout the project.

The final effect allows users to select a theme and see the UI instantly reflect the chosen colors; the article also displays the project directory layout.

src
├── App.vue
├── main.js
├── router
│   └── index.js
├── store
│   └── index.js
├── style
│   ├── settings
│   │   └── variable.scss   // style variables
│   └── theme
│       ├── default
│       ├── index.scss      // theme entry
│       └── old
└── views
    ├── Home.vue   // theme switch page
    ├── List.vue
    └── Mine.vue

First, the SCSS compilation environment is set up by installing the necessary packages.

npm i sass
// sass‑loader must be a specific version; newer versions cause "this.getOptions" errors
npm i -D [email protected]
npm i -S normalize.css

Common visual parameters such as primary colors, font sizes, and weights are defined as SCSS variables.

// ./style/settings/variable.scss
$info: #17a2b8 !default;
$danger: #dc3545 !default;
$font-size-base: 1rem !default;
$font-size-lg: $font-size-base * 1.25 !default;
$font-weight-normal: 400 !default;
$font-weight-bold: 600 !default;

A theme map groups these variables for each theme (e.g., default and old ) and is imported in a single entry file.

// ./style/theme/index.scss
@import "../settings/variable.scss";
$themes-color: (
  default: (
    color: $info,
    font-weight: $font-weight-normal,
    font-size: $font-size-lg,
  ),
  old: (
    color: $danger,
    font-weight: $font-weight-bold,
    font-size: $font-size-slg,
  ),
);

To avoid importing the variables manually in each component, vue.config.js is configured to prepend the theme entry globally.

module.exports = {
  css: {
    loaderOptions: {
      scss: {
        additionalData: `@import "@/style/theme/index.scss";`
      }
    }
  }
};

Theme switching is performed by setting a data-theme attribute on body (defaulted in App.vue ) and by generating a theme array at build time using a custom webpack plugin.

// vue.config.js (custom plugin)
const fs = require('fs');
const webpack = require('webpack');
const themeFiles = fs.readdirSync('./src/style/theme');
let ThemesArr = [];
themeFiles.forEach(item => {
  if (fs.lstatSync(`./src/style/theme/${item}`).isDirectory()) {
    ThemesArr.push(item);
  }
});
module.exports = {
  configureWebpack: config => ({
    plugins: [new webpack.DefinePlugin({ THEMEARR: JSON.stringify(ThemesArr) })]
  })
};

In Home.vue the selected theme index is stored, and the data-theme attribute is updated when the user confirms a new theme.

// Home.vue methods
methods: {
  onConfirm(currentTheme) {
    this.currentTheme = currentTheme;
    this.showPicker = false;
    this.currentThemeIndex = this.themeValue.findIndex(t => t === currentTheme);
    document.getElementsByTagName('body')[0].setAttribute('data-theme', THEMEARR[this.currentThemeIndex]);
  }
}

CSS custom properties can also be used directly; selectors such as [data-theme="default"] .t-list-title read the variables defined with --foo and --bar .

/* default.scss */
[data-theme="default"] .t-list-title, [data-theme="default"] .t-list-sub-title, [data-theme="default"] .t-list-info {
  color: var(--foo);
  font-weight: 400;
  font-size: 1rem * 1.25;
}

The article also covers advanced SCSS techniques useful for theme generation: @each loops over lists and maps, map-get , map-merge , and the @content directive inside mixins, illustrated with a themify mixin that builds a $theme‑map and provides a themed() function for property lookup.

// ./Home.vue mixin example
@mixin themify() {
  @each $theme-name, $map in $themes-color {
    [data-theme="#{$theme-name}"] & {
      $theme-map: () !global;
      @each $key, $value in $map {
        $theme-map: map-merge($theme-map, ($key: $value)) !global;
      }
      @content;
    }
  }
}
@function themed($key) { @return map-get($theme-map, $key); }
.t-list-title, .t-list-sub-title, .t-list-info {
  @include themify() {
    color: themed("color");
    font-weight: themed("font-weight");
    font-size: themed("font-size");
  }
}

All source code is available at https://github.com/AshesOfHistory/test-skin-refresh , and the article concludes that the reader now understands SCSS basics, how to combine them to achieve one‑click skin changes, and the core principles behind dynamic theming.

Frontend DevelopmentVueWebpackCSS variablesscsstheme switching
政采云技术
Written by

政采云技术

ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining us.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.