DEV Community

Cover image for Dynamic component styles in Vue (Composition API and TypeScript) using Tailwind CSS and Lookup tables
Liam Hall
Liam Hall

Posted on • Originally published at Medium

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

This article will assume some knowledge of Vue

You can find the options API 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 Vue with the "manually select features" option, ensuring to check "Choose Vue version" and "TypeScript" from the features list, the rest can be left as deffault. 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;
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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,
    ],
};
Enter fullscreen mode Exit fullscreen mode

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 lang="ts">
    import { defineComponent, PropType } from 'vue'

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

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

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.ts. Inside Button.ts 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'
}
Enter fullscreen mode Exit fullscreen mode

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 lang="ts">
/**/
import { ButtonColors } from '../validators/Button'

export default defineComponent({/**/})
</script>
Enter fullscreen mode Exit fullscreen mode

Next in our color prop we need to let TypesScript know that the string should be equal to one of the keys of our ButtonColors lookup table and replace the array current array of strings with an array of keys from the ButtonColors lookup table:

<script lang="ts">
    export default defineComponent({
        /**/
        props: {
            color: {
                required: false,
                type: String as PropType<keyof typeof ButtonColors>,
                default: 'primary',
                validator: (value : string ) => {
                    return Object.keys(ButtonColors).includes(value)
                }
            }
        }
        /**/
    })
</script>
Enter fullscreen mode Exit fullscreen mode

Now we can create a computed property to handle the dynamic classes in the component template, first we need to import computed from vue, in the same way we have with defineComponent and PropType:

<script>
    import { defineComponent, PropType, computed } from 'vue'
    /**/
</script>
Enter fullscreen mode Exit fullscreen mode

Now we can use the setup method and pass it props as a parameter to allow us to set up a computed buttonColor property like so:

<script>
/**/
export default defineComponent({
    /**/
    setup(props) {
        const buttonColor = computed(() : string  => {
            return ButtonColors[props.color]
        })

        return {
            buttonColor
        }
    }
})
</script>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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 lang="ts">
    import { defineComponent, PropType, computed } from 'vue'

    import { ButtonColors } from '../validators/Button'

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

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

        setup(props) {
            const buttonColor = computed(() : string  => {
                return ButtonColors[props.color]
            })

            return {
                buttonColor
            }
        }
    })
</script>
Enter fullscreen mode Exit fullscreen mode

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/*.ts'
    ]
/**/
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)