DEV Community

Cover image for Dynamic component styles in Nuxt using Tailwind CSS and Lookup tables
Liam Hall
Liam Hall

Posted on • Updated on • Originally published at Medium

Dynamic component styles in Nuxt using Tailwind CSS and Lookup tables

This article will assume some knowledge of Nuxt / Vue

Tailwind, Nuxt, 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 and you can use it in Nuxt too. When creating a new project in Nuxt and selecting the Tailwind preset, PurgeCSS will be installed automatically, although you can utilize it in any project using the nuxt-purgecss build module.

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 Nuxt project with Tailwind CSS

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

npx create-nuxt-app <your-project-name>

To keep things simple we'll use the default settings other than ensuring we select Tailwind CSS from the UI framework.

Dynamic component styles in Nuxt with Tailwind

One of the key features of components in Nuxt (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>
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 root of our project 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'
}
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>
import { ButtonColors } from '~/validators/Button'

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

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)
},
/**/
Enter fullscreen mode Exit fullscreen mode

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>
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>
    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>
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 object, find the content array and add 'validators/*.js', this will tell PurgeCSS to check for styles in our validators folder, our final purge object should look something like this:

purge: {
    // Learn more on https://tailwindcss.com/docs/controlling-file-size/#removing-unused-css
    enabled: process.env.NODE_ENV === 'production',
    content: [
        'components/**/*.vue',
        'layouts/**/*.vue',
        'pages/**/*.vue',
        'plugins/**/*.js',
        'validators/*.js',
        'nuxt.config.js'
    ]
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

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

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

Top comments (0)