Date
2 min read

Creating Dynamic Theme Colour Switching with TailwindCSS and Vue.js

As developers, we tend to grant clients/customers too much control, however, on the occasion there is certain logic that they can have access to. Here’s an example on how I integrated dynamic theme colour control using Vue and Tailwind.

Below using native CSS, we can define a root variable, such as:

@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

@layer base {
  :root {
    /* Primary */
    --colour-theme-primary: 211, 84, 0; /* #d35400 */
  }
}

Now in newer versions of Tailwind we then have control over the opacity of a colour so it’s more ideal to have colour values as RGB, then we can make use of the below syntax:

<button class="bg-sky-500/100 ..."></button>
<button class="bg-sky-500/75 ..."></button>
<button class="bg-sky-500/50 ..."></button>
const colors = require('tailwindcss/colors');

function withOpacity(variableName) {
    return ({opacityValue}) => {
        if (opacityValue !== undefined) {
            return `rgba(var(${variableName}), ${opacityValue})`;
        }
        return `rgb(var(${variableName}))`;
    };
}

/** @type {import('tailwindcss').Config} */
module.exports = {
    theme: {
        colors: {
            ...colors,
            theme: {
                // Primary
                'primary': withOpacity('--colour-theme-primary'),
            },
        },
    },
};

Here we can adopt a few approaches into integrating this into Vue, however, this is based on checking local storage of the potential changed value and always defaulting to the current set within the CSS.

Note that out-of-the-box, the <input> element has a color type that lets a user specify a color, using a nice visual colour picker that returns a hex value which we’ll convert to RGB using some custom methods.

<template>
	<input
			ref="colourpicker"
			v-model="theme"
			type="color"
	>
</template>

<script>
export default {
    data() {
        return {
            theme: null,
        };
    },

    watch: {
        theme(val) {
            document.querySelector(':root').style.setProperty(
                '--colour-theme-primary',
                this.hexToRgb(val),
            );

            localStorage.setItem('colour-theme-primary', val);
        },
    },

    mounted() {
        if (localStorage.getItem('colour-theme-primary')) {
            this.theme = localStorage.getItem('colour-theme-primary');
        } else {
            this.theme = this.rgbToHex(
                getComputedStyle(document.querySelector(':root'))
                    .getPropertyValue('--colour-theme-primary'),
            );
        }
    },
	
	methods: {
        hexToRgb(hex) {
            const aRgbHex = hex.substring(1).match(/.{1,2}/g);

            const aRgb = [
                parseInt(aRgbHex[0], 16),
                parseInt(aRgbHex[1], 16),
                parseInt(aRgbHex[2], 16),
            ];

            return aRgb.join(', ');
        },

        rgbToHex(rgb) {
            const result = rgb.split(',');

            return '#' + (1 << 24 | result[0] << 16 | result[1] << 8 | result[2]).toString(16).slice(1);
        },
    },
};
</script>

As an alternative, there’s always the option to pass in the theme colour as a prop to the Vue component.

Let's work together 🤝

Line
Christopher Kelker

Chriscreates