DEV Community

loading...
Cover image for Dynamic component styles in Vue (Options API) using Tailwind CSS and Lookup tables

Dynamic component styles in Vue (Options API) using Tailwind CSS and Lookup tables

wearethreebears profile image Liam Hall Originally published at Medium ・5 min read

This article will assume some knowledge of Vue

You can find the Composition API with TypeScript version here.

Last week I wrote an article about how I choose to utilize lookup tables when working with dynamic component styles in Nuxt, which you can find here. This week I've been working with Vue 3 and just like when using Nuxt, in Vue I utilize lookup tables when working with dynamic component styles, here's how:

Tailwind and PurgeCSS

Tailwind CSS is one of the hottest topics in frontend development right now. A utility first CSS framework, created by Adam Wathan, which over the past few years has grown from side project to successful business. If you've ever used Tailwind, you may be aware that it utilizes PurgeCSS at build time to prune unused styles and create a slimline stylesheet made up of only the classes used in your web application. Many frameworks now make use of PurgeCSS to remove unnecessary bulk from production stylesheets at build time.

PurgeCSS is a fantastic plugin, however, it cannot parse or run JavaScript and in most cases is only used at build time. Because of this, it can lead to unexpected inconsistencies between development and production environments if used incorrectly.

Starting a fresh Vue project with Tailwind CSS

Let's begin by creating a fresh Vue installation by opening your terminal and running the following command:

vue create <your-project-name>

We'll follow the CLI instructions to set up a default Vue 3 project. Once our project has finished setting up, we can navigate to the root directory and install Tailwind CSS with the following command:

npm install tailwindcss

Once Tailwind CSS has successfully installed, we'll need to create our tailwind.config.js using the following command:

npx tailwindcss init

When the tailwind.config.js file has been created we will need to configure it to scan our .vue files for classes. First we'll uncomment the properties in the future object, this will make upgrading easier in future. Next, within the purge array, add the following line:

'src/**/*.vue',

Now we can
create our Tailwind stylesheet. Navigate to the src/assets folder, create a new directory called css and within it create a new file called styles.css and populate it with the Tailwind CSS imports:

@tailwind base;
@tailwind components;
@tailwind utilities;

Now that the stylesheet is set up we can import it into our application by opening the main.js file from within the src directory and adding the following line:

import '@/assets/css/styles.css';

Finally we need to create our PurgeCSS config file, again in the project root, create a new file, this time called postcss.config.js and configure it with the following code:

// postcss.config.js

const autoprefixer = require('autoprefixer');
const tailwindcss = require('tailwindcss');

module.exports = {
    plugins: [
        tailwindcss,
        autoprefixer,
    ],
};

Dynamic component styles in Vue with Tailwind

One of the key features of components in Vue is the ability to pass props. Props are custom attributes passed to a component that can be used to control appearance and function. Let's look at creating a simple button component using Tailwind that accepts two colorways, 'primary' and 'secondary':

<template>
    <button 
        class="px-4 py-2 text-center transition-colors duration-300 ease-in-out border border-solid rounded shadow-md"
        :class="{ 
            'bg-blue-800 text-white border-blue-800 hover:bg-transparent hover:text-blue-800 hover:border-blue-800' : color == 'primary',
            'bg-transparent text-blue-800 border-blue-800 hover:bg-blue-800 hover:text-white hover:border-blue-800' : color == 'secondary'
        }"
    >
        <slot />
    </button>
</template>

<script>
    export default {
        name: 'component--button',

        props: {
            color: {
                required: false,
                type: String,
                default: 'primary',
                validator: value => {
                    return ['primary', 'secondary'].includes(value)
                }
            }
        }
    }
</script>

So we have our button component that accepts 2 dynamic colorways, 'primary' and 'secondary', exactly as we'd set out, however even in this simple component it's easy to see how these dynamic styles could spiral out of control in more complex components. We also have a color props validator which we have to manually keep in sync with the dynamic styles in the template.

Extracting styles and keeping validators synced with Lookup tables

If you haven't heard of a lookup table, a lookup table is a simple key-value pair object we can use to match keys to data. We can take advantage of lookup tables to extract the dynamic styles and ensure our validator always stays in sync with those dynamic styles.

For this example, we'll be creating a new folder in the src directory called validators to store our lookup tables, although the same technique could be used to make use of lookup tables within the component file if preferred. Once you've created a new folder called validators, create a new file inside called Button.js. Inside Button.js we are going to export a const called ButtonColors, a lookup table which will contain our key-value pairs for our dynamic styles, like so:

export const ButtonColors = {
    'primary': 'bg-blue-800 text-white border-blue-800 hover:bg-transparent hover:text-blue-800 hover:border-blue-800',
    'secondary': 'bg-transparent text-blue-800 border-blue-800 hover:bg-blue-800 hover:text-white hover:border-blue-800'
}

Now we have extracted our dynamic styles to a lookup table we need to make a couple of changes to our component, firstly, beneath the opening script tag we need to import our ButtonColors const:

<script>
import { ButtonColors } from '@/validators/Button'

export default {/**/}
</script>

Next in our color props validator, replace the array with an array of keys from the ButtonColors lookup table:

/**/
validator: (value) => {
    return Object.keys(ButtonColors).includes(value)
},
/**/

Now we can create a computed property to handle the dynamic classes in the component template:

<script>
/**/
export default {
    /**/
    computed: {
        buttonColor() {
            return ButtonColors[this.color]
        },
    }
}
</script>

We can then replace all of the dynamic classes in the template with our new computed property:

<template>
    <button 
        class="px-4 py-2 text-center transition-colors duration-300 ease-in-out border border-solid rounded shadow-md"
        :class="[buttonColor]"
    >
        <slot />
    </button>
</template>

Altogether that should give us a component template that looks like this:

<template>
    <button 
        class="px-4 py-2 text-center transition-colors duration-300 ease-in-out border border-solid rounded shadow-md"
        :class="[buttonColor]"
    >
        <slot />
    </button>
</template>

<script>
    import { ButtonColors } from '@/validators/Button'

    export default {
        name: 'component--button',

        props: {
            color: {
                required: false,
                type: String,
                default: 'primary',
                validator: value => {
                    return Object.keys(ButtonColors).includes(value)
                }
            }
        },

        computed: {
            buttonColor() {
                return ButtonColors[this.color]
            },
        }
    }
</script>

Everything is looking great, our dynamic styles are extracted and our validators will automatically stay in sync with any new dynamic styles we add, however unfortunately at the moment our component will still not be styled as expected in production. Thankfully, there's a very simple fix, open up tailwind.config.js from the root of the project and within the purge array, add 'src/validators/*.js', this will tell PurgeCSS to check for styles in our validators folder, our final purge object should look something like this:

module.exports = {
/**/
    purge: [
        'src/**/*.vue',
        'src/validators/*.js'
    ]
/**/
}

Testing

If you'd like to test your lookup tables are working correctly in production you can test your project in production locally. Begin by installing the Node.js static file server:

npm install -g serve

Once installed, navigate to the root of your project and run:

serve -s dist

Conclusion

Hopefully, you've found this a useful exercise in cleaning up your dynamic classes in Vue options API, Tailwind, and PurgeCSS.

If you’ve found this article useful, please follow me on Medium, Dev.to and/ or Twitter.

Discussion

pic
Editor guide